Merge branch 'main' into dependabot/pip/numpy-gte-1.17.0-and-lt-3
diff --git a/.coveragerc b/.coveragerc
index 55838fc..ca605f7 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -3,6 +3,7 @@
[report]
omit =
+ */astroid/__main__.py
*/tests/*
*/tmp*/*
exclude_lines =
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index 9fbc070..d81e593 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -6,14 +6,14 @@
- labeled
permissions:
- actions: write
- contents: write
- pull-requests: write
+ contents: read
jobs:
backport:
name: Backport
runs-on: ubuntu-latest
+ environment:
+ name: Backport
# Only react to merged PRs for security reasons.
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
if: >
@@ -25,6 +25,16 @@
)
)
steps:
- - uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # v2.0.4
+ - uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
+ id: app-token
with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
+ app-id: ${{ vars.BACKPORT_APP_ID }}
+ private-key: ${{ secrets.PRIVATE_KEY }}
+ permission-contents: write # push branch to Github
+ permission-pull-requests: write # create PR / add comment for manual backport
+ permission-workflows: write # modify files in .github/workflows
+ - uses: pylint-dev/backport@6accae9e09c5ad1bc3a0b56adf37c45357e7bcdc # v2.1.3
+ with:
+ github_token: ${{ steps.app-token.outputs.token }}
+ user_name: ${{ vars.BACKPORT_USER_NAME }}
+ user_email: ${{ vars.BACKPORT_USER_EMAIL }}
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 7c4e1b5..730254f 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -5,7 +5,8 @@
branches:
- main
- 2.*
- pull_request: ~
+ pull_request:
+ workflow_dispatch:
env:
CACHE_VERSION: 3
@@ -23,11 +24,13 @@
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- - name: Check out code from GitHub
- uses: actions/checkout@v4.2.2
- - name: Set up Python ${{ env.DEFAULT_PYTHON }}
+ - &checkout
+ name: Check out code from GitHub
+ uses: actions/checkout@v6.0.2
+ - &setup-python-default
+ name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
- uses: actions/setup-python@v5.4.0
+ uses: actions/setup-python@v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -37,9 +40,10 @@
echo "key=base-venv-${{ env.CACHE_VERSION }}-${{
hashFiles('pyproject.toml', 'requirements_dev.txt',
'requirements_full.txt', 'requirements_minimal.txt') }}" >> $GITHUB_OUTPUT
- - name: Restore Python virtual environment
+ - &cache-python
+ name: Restore Python virtual environment
id: cache-venv
- uses: actions/cache@v4.2.2
+ uses: actions/cache@v5.0.3
with:
path: venv
key: >-
@@ -50,7 +54,7 @@
run: |
python -m venv venv
. venv/bin/activate
- python -m pip install -U pip setuptools wheel
+ python -m pip install -U pip
pip install -U -r requirements_full.txt
- name: Generate pre-commit restore key
id: generate-pre-commit-key
@@ -59,7 +63,7 @@
hashFiles('.pre-commit-config.yaml') }}" >> $GITHUB_OUTPUT
- name: Restore pre-commit environment
id: cache-precommit
- uses: actions/cache@v4.2.2
+ uses: actions/cache@v5.0.3
with:
path: ${{ env.PRE_COMMIT_CACHE }}
key: >-
@@ -72,7 +76,7 @@
- name: Run pre-commit checks
run: |
. venv/bin/activate
- pre-commit run pylint --all-files
+ pre-commit run --hook-stage manual pylint-ci --all-files
tests-linux:
name: tests / run / ${{ matrix.python-version }} / Linux
@@ -81,17 +85,17 @@
strategy:
fail-fast: false
matrix:
- python-version: [3.9, "3.10", "3.11", "3.12", "3.13"]
+ python-version: &matrix-python-version ["3.10", "3.11", "3.12", "3.13", "3.14"]
outputs:
python-key: ${{ steps.generate-python-key.outputs.key }}
steps:
- - name: Check out code from GitHub
- uses: actions/checkout@v4.2.2
+ - *checkout
- name: Set up Python ${{ matrix.python-version }}
id: python
- uses: actions/setup-python@v5.4.0
+ uses: actions/setup-python@v6.2.0
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
check-latest: true
- name: Install Qt
if: ${{ matrix.python-version == '3.10' }}
@@ -104,14 +108,7 @@
echo "key=${{ env.KEY_PREFIX }}-${{ env.CACHE_VERSION }}-${{
hashFiles('pyproject.toml', 'requirements_dev.txt',
'requirements_full.txt', 'requirements_minimal.txt') }}" >> $GITHUB_OUTPUT
- - name: Restore Python virtual environment
- id: cache-venv
- uses: actions/cache@v4.2.2
- with:
- path: venv
- key: >-
- ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
- steps.generate-python-key.outputs.key }}
+ - *cache-python
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
@@ -125,7 +122,7 @@
. venv/bin/activate
pytest --cov
- name: Upload coverage artifact
- uses: actions/upload-artifact@v4.6.1
+ uses: &actions-upload-artifact actions/upload-artifact@v6.0.0
with:
name: coverage-linux-${{ matrix.python-version }}
path: .coverage
@@ -135,44 +132,38 @@
name: tests / run / ${{ matrix.python-version }} / Windows
runs-on: windows-latest
timeout-minutes: 20
- needs: tests-linux
+ needs: [tests-linux]
strategy:
fail-fast: false
matrix:
- python-version: [3.9, "3.10", "3.11", "3.12", "3.13"]
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- name: Set temp directory
run: echo "TEMP=$env:USERPROFILE\AppData\Local\Temp" >> $env:GITHUB_ENV
# Workaround to set correct temp directory on Windows
# https://github.com/actions/virtual-environments/issues/712
- - name: Check out code from GitHub
- uses: actions/checkout@v4.2.2
- - name: Set up Python ${{ matrix.python-version }}
+ - *checkout
+ - &setup-python-matrix
+ name: Set up Python ${{ matrix.python-version }}
id: python
- uses: actions/setup-python@v5.4.0
+ uses: actions/setup-python@v6.2.0
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
check-latest: true
- name: Generate partial Python venv restore key
id: generate-python-key
run: >-
echo "key=${{ env.KEY_PREFIX }}-${{ env.CACHE_VERSION }}-${{
hashFiles('pyproject.toml', 'requirements_dev.txt',
- 'requirements_full.txt', 'requirements_minimal.txt') }}" >> $GITHUB_OUTPUT
- - name: Restore Python virtual environment
- id: cache-venv
- uses: actions/cache@v4.2.2
- with:
- path: venv
- key: >-
- ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
- steps.generate-python-key.outputs.key }}
+ 'requirements_full.txt', 'requirements_minimal.txt') }}" >> $env:GITHUB_OUTPUT
+ - *cache-python
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
python -m venv venv
. venv\\Scripts\\activate
- python -m pip install -U pip setuptools wheel
+ python -m pip install -U pip
pip install -U -r requirements_full.txt
pip install -e .
- name: Run pytest
@@ -180,7 +171,7 @@
. venv\\Scripts\\activate
pytest --cov
- name: Upload coverage artifact
- uses: actions/upload-artifact@v4.6.1
+ uses: *actions-upload-artifact
with:
name: coverage-windows-${{ matrix.python-version }}
path: .coverage
@@ -194,36 +185,23 @@
fail-fast: false
matrix:
# We only test on the lowest and highest supported PyPy versions
- python-version: ["pypy3.9", "pypy3.10"]
+ python-version: ["pypy3.10"]
steps:
- - name: Check out code from GitHub
- uses: actions/checkout@v4.2.2
- - name: Set up Python ${{ matrix.python-version }}
- id: python
- uses: actions/setup-python@v5.4.0
- with:
- python-version: ${{ matrix.python-version }}
- check-latest: true
+ - *checkout
+ - *setup-python-matrix
- name: Generate partial Python venv restore key
id: generate-python-key
run: >-
echo "key=${{ env.KEY_PREFIX }}-${{ env.CACHE_VERSION }}-${{
hashFiles('pyproject.toml', 'requirements_minimal.txt')
}}" >> $GITHUB_OUTPUT
- - name: Restore Python virtual environment
- id: cache-venv
- uses: actions/cache@v4.2.2
- with:
- path: venv
- key: >-
- ${{ runner.os }}-${{ matrix.python-version }}-${{
- steps.generate-python-key.outputs.key }}
+ - *cache-python
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
run: |
python -m venv venv
. venv/bin/activate
- python -m pip install -U pip setuptools wheel
+ python -m pip install -U pip
pip install -U -r requirements_minimal.txt
pip install -e .
- name: Run pytest
@@ -231,7 +209,7 @@
. venv/bin/activate
pytest --cov
- name: Upload coverage artifact
- uses: actions/upload-artifact@v4.6.1
+ uses: *actions-upload-artifact
with:
name: coverage-pypy-${{ matrix.python-version }}
path: .coverage
@@ -243,23 +221,17 @@
timeout-minutes: 10
needs: ["tests-linux", "tests-windows", "tests-pypy"]
steps:
- - name: Check out code from GitHub
- uses: actions/checkout@v4.2.2
- - name: Set up Python 3.13
- id: python
- uses: actions/setup-python@v5.4.0
- with:
- python-version: "3.13"
- check-latest: true
+ - *checkout
+ - *setup-python-default
- name: Install dependencies
run: pip install -U -r requirements_minimal.txt
- name: Download all coverage artifacts
- uses: actions/download-artifact@v4.1.9
+ uses: actions/download-artifact@v7.0.0
- name: Combine Linux coverage results
run: |
coverage combine coverage-linux*/.coverage
coverage xml -o coverage-linux.xml
- - uses: codecov/codecov-action@v5
+ - uses: &actions-codecov codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
@@ -270,7 +242,7 @@
run: |
coverage combine coverage-windows*/.coverage
coverage xml -o coverage-windows.xml
- - uses: codecov/codecov-action@v5
+ - uses: *actions-codecov
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
@@ -281,7 +253,7 @@
run: |
coverage combine coverage-pypy*/.coverage
coverage xml -o coverage-pypy.xml
- - uses: codecov/codecov-action@v5
+ - uses: *actions-codecov
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 68acfd4..a569355 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -46,11 +46,11 @@
steps:
- name: Checkout repository
- uses: actions/checkout@v4.2.2
+ uses: actions/checkout@v6.0.2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v3
+ uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -61,7 +61,7 @@
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@v3
+ uses: github/codeql-action/autobuild@v4
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -75,4 +75,4 @@
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3
+ uses: github/codeql-action/analyze@v4
diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml
deleted file mode 100644
index 4aa24d6..0000000
--- a/.github/workflows/release-tests.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-name: Release tests
-
-on: workflow_dispatch
-
-permissions:
- contents: read
-
-jobs:
- virtualenv-15-windows-test:
- # Regression test added in https://github.com/pylint-dev/astroid/pull/1386
- name: Regression test for virtualenv==15.1.0 on Windows
- runs-on: windows-latest
- timeout-minutes: 5
- steps:
- - name: Check out code from GitHub
- uses: actions/checkout@v4.2.2
- - name: Set up Python 3.9
- id: python
- uses: actions/setup-python@v5.4.0
- with:
- # virtualenv 15.1.0 cannot be installed on Python 3.10+
- python-version: 3.9
- env:
- PIP_TRUSTED_HOST: "pypi.python.org pypi.org files.pythonhosted.org"
- - name: Create Python virtual environment with virtualenv==15.1.0
- env:
- PIP_TRUSTED_HOST: "pypi.python.org pypi.org files.pythonhosted.org"
- run: |
- python -m pip install virtualenv==15.1.0
- python -m virtualenv venv2
- . venv2\scripts\activate
- python -m pip install pylint
- python -m pip install -e .
- - name: Test no import-error from distutils.util
- run: |
- . venv2\scripts\activate
- echo "import distutils.util # pylint: disable=unused-import" > test.py
- pylint test.py
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index eac1b86..d0ca1ba 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,10 +18,10 @@
if: github.event_name == 'release'
steps:
- name: Check out code from Github
- uses: actions/checkout@v4.2.2
+ uses: actions/checkout@v6.0.2
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
- uses: actions/setup-python@v5.4.0
+ uses: actions/setup-python@v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -34,7 +34,7 @@
run: |
python -m build
- name: Upload release assets
- uses: actions/upload-artifact@v4.6.1
+ uses: actions/upload-artifact@v6.0.0
with:
name: release-assets
path: dist/
@@ -50,7 +50,7 @@
id-token: write
steps:
- name: Download release assets
- uses: actions/download-artifact@v4.1.9
+ uses: actions/download-artifact@v7.0.0
with:
name: release-assets
path: dist/
@@ -67,13 +67,13 @@
id-token: write
steps:
- name: Download release assets
- uses: actions/download-artifact@v4.1.9
+ uses: actions/download-artifact@v7.0.0
with:
name: release-assets
path: dist/
- name: Sign the dists with Sigstore and upload assets to Github release
if: github.event_name == 'release'
- uses: sigstore/gh-action-sigstore-python@v3.0.0
+ uses: sigstore/gh-action-sigstore-python@v3.2.0
with:
inputs: |
./dist/*.tar.gz
diff --git a/.github/workflows/test-pylint.yml b/.github/workflows/test-pylint.yml
new file mode 100644
index 0000000..2b4b104
--- /dev/null
+++ b/.github/workflows/test-pylint.yml
@@ -0,0 +1,11 @@
+name: Test pylint
+
+on:
+ pull_request:
+
+jobs:
+ test-pylint-with-astroid-sha:
+ uses: pylint-dev/pylint/.github/workflows/tests.yaml@main
+ with:
+ repository: pylint-dev/pylint
+ astroid_sha: ${{ github.sha }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1e8b2b5..c4cdf8a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,16 +3,16 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v5.0.0
+ rev: v6.0.0
hooks:
- id: trailing-whitespace
exclude: .github/|tests/testdata
- id: end-of-file-fixer
exclude: tests/testdata
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: "v0.11.0"
+ rev: "v0.15.2"
hooks:
- - id: ruff
+ - id: ruff-check
args: ["--fix"]
- repo: https://github.com/Pierre-Sassoulas/copyright_notice_precommit
rev: 0.1.2
@@ -22,18 +22,18 @@
exclude: tests/testdata|setup.py
types: [python]
- repo: https://github.com/asottile/pyupgrade
- rev: v3.19.1
+ rev: v3.21.2
hooks:
- id: pyupgrade
exclude: tests/testdata
- args: [--py39-plus]
+ args: [--py310-plus]
- repo: https://github.com/Pierre-Sassoulas/black-disable-checker/
rev: v1.1.3
hooks:
- id: black-disable-checker
exclude: tests/test_nodes_lineno.py
- - repo: https://github.com/psf/black
- rev: 25.1.0
+ - repo: https://github.com/psf/black-pre-commit-mirror
+ rev: 26.1.0
hooks:
- id: black
args: [--safe, --quiet]
@@ -49,11 +49,26 @@
"-rn",
"-sn",
"--rcfile=pylintrc",
+ # "--load-plugins=pylint.extensions.docparams", We're not ready for that
+ ]
+ # We define an additional manual step to allow running pylint
+ # with the proper output for CI.
+ - id: pylint
+ alias: pylint-ci
+ name: pylint
+ entry: pylint
+ language: system
+ types: [python]
+ args: [
+ "-rn",
+ "-sn",
+ "--rcfile=pylintrc",
"--output-format=github",
# "--load-plugins=pylint.extensions.docparams", We're not ready for that
]
+ stages: [manual]
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v1.15.0
+ rev: v1.19.1
hooks:
- id: mypy
language: python
@@ -61,11 +76,11 @@
require_serial: true
additional_dependencies: ["types-typed-ast"]
- repo: https://github.com/rbubley/mirrors-prettier
- rev: v3.5.3
+ rev: v3.8.1
hooks:
- id: prettier
args: [--prose-wrap=always, --print-width=88]
- repo: https://github.com/tox-dev/pyproject-fmt
- rev: "v2.5.1"
+ rev: "v2.16.2"
hooks:
- id: pyproject-fmt
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 83d4651..e9fd059 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -14,8 +14,8 @@
Maintainers
-----------
- Pierre Sassoulas <pierre.sassoulas@gmail.com>
-- Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
- Jacob Walls <jacobtylerwalls@gmail.com>
+- Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
- Marc Mueller <30130371+cdce8p@users.noreply.github.com>
- Hippo91 <guillaume.peillex@gmail.com>
- Bryce Guinta <bryce.paul.guinta@gmail.com>
@@ -35,16 +35,19 @@
- correctmost <134317971+correctmost@users.noreply.github.com>
- Andrew Haigh <hello@nelf.in>
- Julien Cristau <julien.cristau@logilab.fr>
-- David Liu <david@cs.toronto.edu>
- Artem Yurchenko <44875844+temyurchenko@users.noreply.github.com>
+- David Liu <david@cs.toronto.edu>
- Alexandre Fayolle <alexandre.fayolle@logilab.fr>
- Eevee (Alex Munroe) <amunroe@yelp.com>
+- Emmanuel Ferdman <emmanuelferdman@gmail.com>
- David Gilman <davidgilman1@gmail.com>
+- Zen Lee <53538590+zenlyj@users.noreply.github.com>
- Tushar Sadhwani <tushar.sadhwani000@gmail.com>
+- Matus Valo <matusvalo@users.noreply.github.com>
- Julien Jehannet <julien.jehannet@logilab.fr>
+- Hugo van Kemenade <hugovk@users.noreply.github.com>
- Calen Pennington <calen.pennington@gmail.com>
- Antonio <antonio@zoftko.com>
-- Hugo van Kemenade <hugovk@users.noreply.github.com>
- Akhil Kamat <akhil.kamat@gmail.com>
- Tim Martin <tim@asymptotic.co.uk>
- Phil Schaf <flying-sheep@web.de>
@@ -59,6 +62,7 @@
- Daniel Harding <dharding@gmail.com>
- Christian Clauss <cclauss@me.com>
- Ville Skyttä <ville.skytta@iki.fi>
+- Synrom <30272537+Synrom@users.noreply.github.com>
- Rene Zhang <rz99@cornell.edu>
- Philip Lorenz <philip@bithub.de>
- Nicolas Chauvat <nicolas.chauvat@logilab.fr>
@@ -72,6 +76,7 @@
- Dani Alcala <112832187+clavedeluna@users.noreply.github.com>
- Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
- tristanlatr <19967168+tristanlatr@users.noreply.github.com>
+- grayjk <grayjk@gmail.com>
- emile@crater.logilab.fr <emile@crater.logilab.fr>
- doranid <ddandd@gmail.com>
- brendanator <brendan.maginnis@gmail.com>
@@ -81,16 +86,19 @@
- Stefan Scherfke <stefan@sofa-rockers.org>
- Sergei Lebedev <185856+superbobry@users.noreply.github.com>
- Saugat Pachhai (सौगात) <suagatchhetri@outlook.com>
+- Robert Hofer <1058012+hofrob@users.noreply.github.com>
- Ram Rachum <ram@rachum.com>
- Pierre-Yves David <pierre-yves.david@logilab.fr>
- Peter Pentchev <roam@ringlet.net>
- Peter Kolbus <peter.kolbus@gmail.com>
- Omer Katz <omer.drow@gmail.com>
- Moises Lopez <moylop260@vauxoo.com>
+- Mitch Harding <mitchell.harding@hpe.com>
- Michal Vasilek <michal@vasilek.cz>
- Keichi Takahashi <keichi.t@me.com>
- Kavins Singh <kavinsingh@hotmail.com>
- Karthikeyan Singaravelan <tir.karthi@gmail.com>
+- Kai Mueller <15907922+kasium@users.noreply.github.com>
- Joshua Cannon <joshdcannon@gmail.com>
- John Vandenberg <jayvdb@gmail.com>
- Jacob Bogdanov <jacob@bogdanov.dev>
@@ -102,26 +110,31 @@
- Anthony Sottile <asottile@umich.edu>
- Alexander Shadchin <alexandr.shadchin@gmail.com>
- wgehalo <wgehalo@gmail.com>
+- tejaschauhan36912 <59693377+tejaschauhan36912@users.noreply.github.com>
- rr- <rr-@sakuya.pl>
- raylu <lurayl@gmail.com>
- plucury <plucury@gmail.com>
+- pavan-msys <149513767+pavan-msys@users.noreply.github.com>
- ostr00000 <ostr00000@gmail.com>
- noah-weingarden <33741795+noah-weingarden@users.noreply.github.com>
- nathannaveen <42319948+nathannaveen@users.noreply.github.com>
- mathieui <mathieui@users.noreply.github.com>
- markmcclain <markmcclain@users.noreply.github.com>
+- jkmnt <git@firewood.fastmail.com>
- ioanatia <ioanatia@users.noreply.github.com>
-- grayjk <grayjk@gmail.com>
- alm <alonme@users.noreply.github.com>
- adam-grant-hendry <59346180+adam-grant-hendry@users.noreply.github.com>
+- aatle <168398276+aatle@users.noreply.github.com>
- Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
- Zac Hatfield-Dodds <Zac-HD@users.noreply.github.com>
+- Youngwook Kim <youngwook.kim@gmail.com>
- Vilnis Termanis <vilnis.termanis@iotics.com>
- Valentin Valls <valentin.valls@esrf.fr>
- Uilian Ries <uilianries@gmail.com>
- Tomas Novak <ext.Tomas.Novak@skoda-auto.cz>
- Thirumal Venkat <me@thirumal.in>
- SupImDos <62866982+SupImDos@users.noreply.github.com>
+- Stéphane Brunner <stephane.brunner@camptocamp.com>
- Stanislav Levin <slev@altlinux.org>
- Simon Hewitt <si@sjhewitt.co.uk>
- Serhiy Storchaka <storchaka@gmail.com>
@@ -133,6 +146,7 @@
- Peter de Blanc <peter@standard.ai>
- Peter Talley <peterctalley@gmail.com>
- Ovidiu Sabou <ovidiu@sabou.org>
+- Oliver Reiche <oliver.reiche@gmail.com>
- Oleh Prypin <oleh@pryp.in>
- Nicolas Noirbent <nicolas@noirbent.fr>
- Neil Girdhar <mistersheik@gmail.com>
@@ -141,16 +155,17 @@
- Mateusz Bysiek <mb@mbdev.pl>
- Matej Aleksandrov <matej.aleksandrov@gmail.com>
- Marcelo Trylesinski <marcelotryle@gmail.com>
+- Low, Zhi Hao <lowzhao@gmail.com>
- Leandro T. C. Melo <ltcmelo@gmail.com>
- Konrad Weihmann <kweihmann@outlook.com>
- Kian Meng, Ang <kianmeng.ang@gmail.com>
-- Kai Mueller <15907922+kasium@users.noreply.github.com>
- Jörg Thalheim <Mic92@users.noreply.github.com>
- Jérome Perrin <perrinjerome@gmail.com>
- JulianJvn <128477611+JulianJvn@users.noreply.github.com>
- Josef Kemetmüller <josef.kemetmueller@gmail.com>
- Jonathan Striebel <jstriebel@users.noreply.github.com>
- John Belmonte <john@neggie.net>
+- Joao Faria <joaovitorfaria.dev@gmail.com>
- Jeff Widman <jeff@jeffwidman.com>
- Jeff Quast <contact@jeffquast.com>
- Jarrad Hope <me@jarradhope.com>
@@ -175,6 +190,7 @@
- Denis Laxalde <denis.laxalde@logilab.fr>
- Deepyaman Datta <deepyaman.datta@utexas.edu>
- David Poirier <david-poirier-csn@users.noreply.github.com>
+- David Foster <david@dafoster.net>
- Dave Hirschfeld <dave.hirschfeld@gmail.com>
- Dave Baum <dbaum@google.com>
- Daniel Martin <daniel.martin@crowdstrike.com>
@@ -185,6 +201,7 @@
- Cole Robinson <crobinso@redhat.com>
- Christoph Reiter <reiter.christoph@gmail.com>
- Chris Philip <chrisp533@gmail.com>
+- Charlie Ringström <34444482+Chasarr@users.noreply.github.com>
- BioGeek <jeroen.vangoey@gmail.com>
- Bianca Power <30207144+biancapower@users.noreply.github.com>
- Benjamin Elven <25181435+S3ntinelX@users.noreply.github.com>
diff --git a/ChangeLog b/ChangeLog
index d730e8e..a6d6335 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -3,10 +3,209 @@
===================
-What's New in astroid 4.0.0?
+What's New in astroid 4.2.0?
============================
Release date: TBA
+
+What's New in astroid 4.1.2?
+============================
+Release date: TBA
+
+* Fix ``RecursionError`` in ``_compute_mro()`` when circular class hierarchies
+ are created through runtime name rebinding. Circular bases are now resolved
+ to the original class instead of recursing.
+
+ Closes #2967
+ Closes pylint-dev/pylint#10821
+
+* Fix ``DuplicateBasesError`` crash in dataclass transform when a class has
+ duplicate bases in its MRO (e.g., ``Protocol`` appearing both directly and
+ indirectly). Catch ``MroError`` at ``.mro()`` call sites in
+ ``brain_dataclasses.py``, consistent with the existing pattern elsewhere.
+
+ Closes #2628
+
+* Fix ``FunctionModel`` returning descriptor attributes for builtin functions.
+
+ Closes #2743
+
+* Catch ``MemoryError`` when inferring f-strings with extremely large format
+ widths (e.g. ``f'{0:11111111111}'``) so that inference yields ``Uninferable``
+ instead of crashing.
+
+ Closes #2762
+
+* Fix ``ValueError`` in ``__str__``/``repr`` and error messages when nodes have
+ extreme values (very long identifiers or large integers). Clamp pprint width
+ to a minimum of 1 and truncate oversized values in error messages.
+
+ Closes #2764
+
+
+What's New in astroid 4.1.1?
+============================
+Release date: 2026-02-22
+
+* Let `UnboundMethodModel` inherit from `FunctionModel` to improve inference of
+ dunder methods for unbound methods.
+
+ Refs #2741
+
+* Filter ``Unknown`` from ``UnboundMethod`` and ``Super`` special attribute
+ lookup to prevent placeholder nodes from leaking during inference.
+
+ Refs #2741
+
+
+What's New in astroid 4.1.0?
+============================
+Release date: 2026-02-08
+
+* Add support for equality constraints (``==``, ``!=``) in inference.
+ Closes pylint-dev/pylint#3632
+ Closes pylint-dev/pylint#3633
+
+* Ensure ``ast.JoinedStr`` nodes are ``Uninferable`` when the ``ast.FormattedValue`` is
+ ``Uninferable``. This prevents ``unexpected-keyword-arg`` messages in Pylint
+ where the ``Uninferable`` string appeared in function arguments that were
+ constructed dynamically.
+
+ Closes pylint-dev/pylint#10822
+
+* Add support for type constraints (`isinstance(x, y)`) in inference.
+
+ Closes pylint-dev/pylint#1162
+ Closes pylint-dev/pylint#4635
+ Closes pylint-dev/pylint#10469
+
+* Make `type.__new__()` raise clear errors instead of returning `None`
+
+* Move object dunder methods from ``FunctionModel`` to ``ObjectModel`` to make them
+ available on all object types, not just functions.
+
+ Closes #2742
+ Closes #2741
+ Closes pylint-dev/pylint#6094
+
+* ``lineno`` and ``end_lineno`` are now available on ``Arguments``.
+
+* Add helper to iterate over all annotations nodes of function arguments,
+ ``Arguments.get_annotations()``.
+
+ Refs #2860
+
+* Skip direct parent when determining the ``Decorator`` frame.
+
+ Refs pylint-dev/pylint#8425
+
+* Add simple command line interface for astroid to output generated AST.
+ Use with ``python -m astroid``.
+
+* Fix incorrect type inference for ``super().method()`` calls that return ``Self``.
+ Previously, astroid would infer the parent class type instead of the child class type,
+ causing pylint E1101 false positives in method chaining scenarios.
+
+ Closes #457
+
+* Add missing ``dtype`` and ``casting`` parameters to ``numpy.concatenate`` brain.
+
+ Closes #2870
+
+* Fix ability to detect .py modules inside PATH directories on Windows
+ described by a UNC path with a trailing backslash (`\`)
+ - Example: modutils.modpath_from_file(filename=r"\\Mac\Code\tests\test_resources.py", path=["\\mac\code\"]) == ['tests', 'test_resources']
+
+* Fix ``random.sample`` inference crash when sequence contains uninferable elements.
+
+ Closes #2518
+
+* Fix ``random.sample`` crash when cloning ``ClassDef`` or ``FunctionDef`` nodes.
+
+ Closes #2923
+
+
+What's New in astroid 4.0.4?
+============================
+Release date: 2026-02-07
+
+* Fix ``is_namespace()`` crash when search locations contain ``pathlib.Path`` objects.
+
+ Closes #2942
+
+What's New in astroid 4.0.3?
+============================
+Release date: 2026-01-03
+
+* Fix inference of ``IfExp`` (ternary expression) nodes to avoid prematurely narrowing
+ results in the face of inference ambiguity.
+
+ Closes #2899
+
+* Fix base class inference for dataclasses using the PEP 695 typing syntax.
+
+ Refs pylint-dev/pylint#10788
+
+
+What's New in astroid 4.0.2?
+============================
+Release date: 2025-11-09
+
+* Handle FunctionDef blockstart_tolineno edge cases correctly.
+
+ Refs #2880
+
+* Add ``HTTPMethod`` enum support to brain module for Python 3.11+.
+
+ Refs pylint-dev/pylint#10624
+ Closes #2877
+
+What's New in astroid 4.0.1?
+============================
+Release date: 2025-10-11
+
+* Suppress ``SyntaxWarning`` for invalid escape sequences and return in finally on
+ Python 3.14 when parsing modules.
+
+* Assign ``Import`` and ``ImportFrom`` nodes to module locals if used with ``global``.
+
+ Closes pylint-dev/pylint#10632
+
+
+What's New in astroid 4.0.0?
+============================
+Release date: 2025-10-05
+
+* Support constraints from ternary expressions in inference.
+
+ Closes pylint-dev/pylint#9729
+
+* Handle deprecated `bool(NotImplemented)` cast in const nodes.
+
+* Add support for boolean truthiness constraints (`x`, `not x`) in inference.
+
+ Closes pylint-dev/pylint#9515
+
+* Fix false positive `invalid-name` on `attrs` classes with `ClassVar` annotated variables.
+
+ Closes pylint-dev/pylint#10525
+
+* Prevent crash when parsing deeply nested parentheses causing MemoryError in python's built-in ast.
+
+ Closes #2643
+
+* Fix crash when inferring namedtuple with invalid field name looking like f-string formatting.
+
+ Closes #2519
+
+* Fix false positive no-member in except * handler.
+
+ Closes pylint-dev/pylint#9056
+
+* Fix crash when comparing invalid dict literal
+
+ Closes #2522
+
* Removed internal functions ``infer_numpy_member``, ``name_looks_like_numpy_member``, and
``attribute_looks_like_numpy_member`` from ``astroid.brain.brain_numpy_utils``.
@@ -32,15 +231,71 @@
Closes #2513
+* Remove support for Python 3.9 (and constant `PY310_PLUS`).
+
+* Include subclasses of standard property classes as `property` decorators
+
+ Closes #10377
+
+* Modify ``astroid.bases`` and ``tests.test_nodes`` to reflect that `enum.property` was added in Python 3.11, not 3.10
+
+* Fix incorrect result in `_get_relative_base_path` when the target directory name starts with the base path
+
+ Closes #2608
+
+* The brain for nose was dropped. nose has been deprecated for 10 years and the brain required some maintenance.
+
+ Refs #2765
+
+* Fix a crash when the root of a node is not a module but is unknown.
+
+ Closes #2672
+
+* Add basic support for ``ast.TemplateStr`` and ``ast.Interpolation`` added in Python 3.14.
+
+ Refs #2789
+
+* Add support for type parameter defaults added in Python 3.13.
+
+* Improve ``as_string()`` representation for ``TypeVar``, ``ParamSpec`` and ``TypeVarTuple`` nodes, as well as
+ type parameter in ``ClassDef``, ``FuncDef`` and ``TypeAlias`` nodes (PEP 695).
+
+* Astroid now correctly supports the ``exceptions`` attribute of ``ExceptionGroup``.
+
+ Closes pylint-dev/pylint#8985
+ Closes pylint-dev/pylint#10558
+
+* Deprecate importing node classes from ``astroid`` directly. This will be removed in v5.
+ It's recommended to import them from ``astroid.nodes`` instead.
+
+ Refs #2837
+
+
+What's New in astroid 3.3.11?
+=============================
+Release date: 2025-07-13
+
+* Fix a crash when parsing an empty arbitrary expression with ``extract_node`` (``extract_node("__()")``).
+
+ Closes #2734
+
+* Fix a crash when parsing a slice called in a decorator on a function that is also decorated with
+ a known ``six`` decorator.
+
+ Closes #2721
What's New in astroid 3.3.10?
=============================
-Release date: TBA
+Release date: 2025-05-10
* Avoid importing submodules sharing names with standard library modules.
Closes #2684
+* Fix bug where ``pylint code.custom_extension`` would analyze ``code.py`` or ``code.pyi`` instead if they existed.
+
+ Closes pylint-dev/pylint#3631
+
What's New in astroid 3.3.9?
============================
@@ -539,11 +794,6 @@
Closes #2305
Closes pylint-dev/pylint#9069
-* Fix a regression in 2.15.7 for ``unsubscriptable-object``.
-
- Closes #2305
- Closes pylint-dev/pylint#9069
-
What's New in astroid 2.15.7?
=============================
diff --git a/README.rst b/README.rst
index 26d2231..0e479b1 100644
--- a/README.rst
+++ b/README.rst
@@ -1,7 +1,7 @@
Astroid
=======
-.. image:: https://codecov.io/gh/pylint-dev/astroid/branch/main/graph/badge.svg?token=Buxy4WptLb
+.. image:: https://codecov.io/gh/pylint-dev/astroid/branch/main/graph/badge.svg
:target: https://codecov.io/gh/pylint-dev/astroid
:alt: Coverage badge from codecov
diff --git a/astroid/__init__.py b/astroid/__init__.py
index f04b4df..abb45cf 100644
--- a/astroid/__init__.py
+++ b/astroid/__init__.py
@@ -30,9 +30,6 @@
* builder contains the class responsible to build astroid trees
"""
-import functools
-import tokenize
-
# isort: off
# We have an isort: off on 'astroid.nodes' because of a circular import.
from astroid.nodes import node_classes, scoped_nodes
@@ -44,7 +41,7 @@
from astroid.bases import BaseInstance, BoundMethod, Instance, UnboundMethod
from astroid.brain.helpers import register_module_extender
from astroid.builder import extract_node, parse
-from astroid.const import PY310_PLUS, Context
+from astroid.const import Context
from astroid.exceptions import (
AstroidBuildingError,
AstroidError,
@@ -83,89 +80,91 @@
from astroid.astroid_manager import MANAGER
from astroid.nodes import (
CONST_CLS,
- AnnAssign,
- Arguments,
- Assert,
- Assign,
- AssignAttr,
- AssignName,
- AsyncFor,
- AsyncFunctionDef,
- AsyncWith,
- Attribute,
- AugAssign,
- Await,
- BinOp,
- BoolOp,
- Break,
- Call,
- ClassDef,
- Compare,
- Comprehension,
- ComprehensionScope,
- Const,
- Continue,
- Decorators,
- DelAttr,
- Delete,
- DelName,
- Dict,
- DictComp,
- DictUnpack,
- EmptyNode,
- EvaluatedObject,
- ExceptHandler,
- Expr,
- For,
- FormattedValue,
- FunctionDef,
- GeneratorExp,
- Global,
- If,
- IfExp,
- Import,
- ImportFrom,
- JoinedStr,
- Keyword,
- Lambda,
- List,
- ListComp,
- Match,
- MatchAs,
- MatchCase,
- MatchClass,
- MatchMapping,
- MatchOr,
- MatchSequence,
- MatchSingleton,
- MatchStar,
- MatchValue,
- Module,
- Name,
- NamedExpr,
- NodeNG,
- Nonlocal,
- ParamSpec,
- Pass,
- Raise,
- Return,
- Set,
- SetComp,
- Slice,
- Starred,
- Subscript,
- Try,
- TryStar,
- Tuple,
- TypeAlias,
- TypeVar,
- TypeVarTuple,
- UnaryOp,
- Unknown,
- While,
- With,
- Yield,
- YieldFrom,
+ AnnAssign as _DEPRECATED_AnnAssign,
+ Arguments as _DEPRECATED_Arguments,
+ Assert as _DEPRECATED_Assert,
+ Assign as _DEPRECATED_Assign,
+ AssignAttr as _DEPRECATED_AssignAttr,
+ AssignName as _DEPRECATED_AssignName,
+ AsyncFor as _DEPRECATED_AsyncFor,
+ AsyncFunctionDef as _DEPRECATED_AsyncFunctionDef,
+ AsyncWith as _DEPRECATED_AsyncWith,
+ Attribute as _DEPRECATED_Attribute,
+ AugAssign as _DEPRECATED_AugAssign,
+ Await as _DEPRECATED_Await,
+ BinOp as _DEPRECATED_BinOp,
+ BoolOp as _DEPRECATED_BoolOp,
+ Break as _DEPRECATED_Break,
+ Call as _DEPRECATED_Call,
+ ClassDef as _DEPRECATED_ClassDef,
+ Compare as _DEPRECATED_Compare,
+ Comprehension as _DEPRECATED_Comprehension,
+ ComprehensionScope as _DEPRECATED_ComprehensionScope,
+ Const as _DEPRECATED_Const,
+ Continue as _DEPRECATED_Continue,
+ Decorators as _DEPRECATED_Decorators,
+ DelAttr as _DEPRECATED_DelAttr,
+ Delete as _DEPRECATED_Delete,
+ DelName as _DEPRECATED_DelName,
+ Dict as _DEPRECATED_Dict,
+ DictComp as _DEPRECATED_DictComp,
+ DictUnpack as _DEPRECATED_DictUnpack,
+ EmptyNode as _DEPRECATED_EmptyNode,
+ EvaluatedObject as _DEPRECATED_EvaluatedObject,
+ ExceptHandler as _DEPRECATED_ExceptHandler,
+ Expr as _DEPRECATED_Expr,
+ For as _DEPRECATED_For,
+ FormattedValue as _DEPRECATED_FormattedValue,
+ FunctionDef as _DEPRECATED_FunctionDef,
+ GeneratorExp as _DEPRECATED_GeneratorExp,
+ Global as _DEPRECATED_Global,
+ If as _DEPRECATED_If,
+ IfExp as _DEPRECATED_IfExp,
+ Import as _DEPRECATED_Import,
+ ImportFrom as _DEPRECATED_ImportFrom,
+ Interpolation as _DEPRECATED_Interpolation,
+ JoinedStr as _DEPRECATED_JoinedStr,
+ Keyword as _DEPRECATED_Keyword,
+ Lambda as _DEPRECATED_Lambda,
+ List as _DEPRECATED_List,
+ ListComp as _DEPRECATED_ListComp,
+ Match as _DEPRECATED_Match,
+ MatchAs as _DEPRECATED_MatchAs,
+ MatchCase as _DEPRECATED_MatchCase,
+ MatchClass as _DEPRECATED_MatchClass,
+ MatchMapping as _DEPRECATED_MatchMapping,
+ MatchOr as _DEPRECATED_MatchOr,
+ MatchSequence as _DEPRECATED_MatchSequence,
+ MatchSingleton as _DEPRECATED_MatchSingleton,
+ MatchStar as _DEPRECATED_MatchStar,
+ MatchValue as _DEPRECATED_MatchValue,
+ Module as _DEPRECATED_Module,
+ Name as _DEPRECATED_Name,
+ NamedExpr as _DEPRECATED_NamedExpr,
+ NodeNG as _DEPRECATED_NodeNG,
+ Nonlocal as _DEPRECATED_Nonlocal,
+ ParamSpec as _DEPRECATED_ParamSpec,
+ Pass as _DEPRECATED_Pass,
+ Raise as _DEPRECATED_Raise,
+ Return as _DEPRECATED_Return,
+ Set as _DEPRECATED_Set,
+ SetComp as _DEPRECATED_SetComp,
+ Slice as _DEPRECATED_Slice,
+ Starred as _DEPRECATED_Starred,
+ Subscript as _DEPRECATED_Subscript,
+ TemplateStr as _DEPRECATED_TemplateStr,
+ Try as _DEPRECATED_Try,
+ TryStar as _DEPRECATED_TryStar,
+ Tuple as _DEPRECATED_Tuple,
+ TypeAlias as _DEPRECATED_TypeAlias,
+ TypeVar as _DEPRECATED_TypeVar,
+ TypeVarTuple as _DEPRECATED_TypeVarTuple,
+ UnaryOp as _DEPRECATED_UnaryOp,
+ Unknown as _DEPRECATED_Unknown,
+ While as _DEPRECATED_While,
+ With as _DEPRECATED_With,
+ Yield as _DEPRECATED_Yield,
+ YieldFrom as _DEPRECATED_YieldFrom,
are_exclusive,
builtin_lookup,
unpack_infer,
@@ -176,11 +175,68 @@
from astroid.util import Uninferable
-# Performance hack for tokenize. See https://bugs.python.org/issue43014
-# Adapted from https://github.com/PyCQA/pycodestyle/pull/993
-if (
- not PY310_PLUS
- and callable(getattr(tokenize, "_compile", None))
- and getattr(tokenize._compile, "__wrapped__", None) is None # type: ignore[attr-defined]
-):
- tokenize._compile = functools.lru_cache(tokenize._compile) # type: ignore[attr-defined]
+__all__ = [
+ "CONST_CLS",
+ "MANAGER",
+ "AstroidBuildingError",
+ "AstroidError",
+ "AstroidImportError",
+ "AstroidIndexError",
+ "AstroidSyntaxError",
+ "AstroidTypeError",
+ "AstroidValueError",
+ "AttributeInferenceError",
+ "BaseInstance",
+ "BoundMethod",
+ "Context",
+ "DuplicateBasesError",
+ "ExceptionInstance",
+ "InconsistentMroError",
+ "InferenceError",
+ "InferenceOverwriteError",
+ "Instance",
+ "MroError",
+ "NameInferenceError",
+ "NoDefault",
+ "NotFoundError",
+ "ParentMissingError",
+ "ResolveError",
+ "StatementMissing",
+ "SuperArgumentTypeError",
+ "SuperError",
+ "TooManyLevelsError",
+ "UnboundMethod",
+ "Uninferable",
+ "UnresolvableName",
+ "UseInferenceDefault",
+ "__version__",
+ "_inference_tip_cached",
+ "are_exclusive",
+ "builtin_lookup",
+ "extract_node",
+ "function_to_method",
+ "inference_tip",
+ "node_classes",
+ "parse",
+ "raw_building",
+ "register_module_extender",
+ "scoped_nodes",
+ "unpack_infer",
+ "version",
+]
+
+
+def __getattr__(name: str):
+ if (val := globals().get(f"_DEPRECATED_{name}")) is None:
+ msg = f"module '{__name__}' has no attribute '{name}"
+ raise AttributeError(msg)
+
+ # pylint: disable-next=import-outside-toplevel
+ import warnings
+
+ msg = (
+ f"importing '{name}' from 'astroid' is deprecated and will be removed in v5, "
+ "import it from 'astroid.nodes' instead"
+ )
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
+ return val
diff --git a/astroid/__main__.py b/astroid/__main__.py
new file mode 100644
index 0000000..90a40d6
--- /dev/null
+++ b/astroid/__main__.py
@@ -0,0 +1,54 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
+
+"""Command line interface for astroid."""
+
+from __future__ import annotations
+
+import sys
+from argparse import ArgumentParser, Namespace
+from collections.abc import Callable, Sequence
+from pathlib import Path
+from typing import cast
+
+import astroid
+
+
+class Arguments(Namespace):
+ func: Callable[[Arguments], int]
+
+
+class ASTParserArguments(Arguments):
+ file: str
+
+
+def parse_ast(args: ASTParserArguments) -> int:
+ if not ((file := Path(args.file)).is_file() and file.suffix in {".py", ".pyi"}):
+ print(f"error: '{file}' does not exist or isn't a Python file")
+ return 1
+
+ tree = astroid.parse(file.read_text(encoding="utf8"))
+ print(tree.repr_tree())
+ return 0
+
+
+def main(argv: Sequence[str] | None = None) -> int:
+ argv = argv or sys.argv[1:]
+ parser = ArgumentParser(description="Command line interface for astroid")
+ subparsers = parser.add_subparsers()
+
+ ast_parser = subparsers.add_parser("ast", help="Print astroid AST")
+ ast_parser.set_defaults(func=parse_ast)
+ ast_parser.add_argument("file", metavar="FILE", help="File to parse")
+
+ args = cast(Arguments, parser.parse_args(argv))
+ if "func" not in args:
+ parser.print_help()
+ return 2
+
+ return args.func(args)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py
index 8af981f..a3e9cb4 100644
--- a/astroid/__pkginfo__.py
+++ b/astroid/__pkginfo__.py
@@ -2,5 +2,5 @@
# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
-__version__ = "4.0.0dev1"
+__version__ = "4.2.0-dev0"
version = __version__
diff --git a/astroid/_backport_stdlib_names.py b/astroid/_backport_stdlib_names.py
deleted file mode 100644
index 901f90b..0000000
--- a/astroid/_backport_stdlib_names.py
+++ /dev/null
@@ -1,352 +0,0 @@
-# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
-# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
-# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
-
-"""
-Shim to support Python versions < 3.10 that don't have sys.stdlib_module_names
-
-These values were created by cherry-picking the commits from
-https://bugs.python.org/issue42955 into each version, but may be updated
-manually if changes are needed.
-"""
-
-import sys
-
-# TODO: Remove this file when Python 3.9 is no longer supported
-
-PY_3_7 = frozenset(
- {
- "__future__",
- "_abc",
- "_ast",
- "_asyncio",
- "_bisect",
- "_blake2",
- "_bootlocale",
- "_bz2",
- "_codecs",
- "_codecs_cn",
- "_codecs_hk",
- "_codecs_iso2022",
- "_codecs_jp",
- "_codecs_kr",
- "_codecs_tw",
- "_collections",
- "_collections_abc",
- "_compat_pickle",
- "_compression",
- "_contextvars",
- "_crypt",
- "_csv",
- "_ctypes",
- "_curses",
- "_curses_panel",
- "_datetime",
- "_dbm",
- "_decimal",
- "_dummy_thread",
- "_elementtree",
- "_functools",
- "_gdbm",
- "_hashlib",
- "_heapq",
- "_imp",
- "_io",
- "_json",
- "_locale",
- "_lsprof",
- "_lzma",
- "_markupbase",
- "_md5",
- "_msi",
- "_multibytecodec",
- "_multiprocessing",
- "_opcode",
- "_operator",
- "_osx_support",
- "_pickle",
- "_posixsubprocess",
- "_py_abc",
- "_pydecimal",
- "_pyio",
- "_queue",
- "_random",
- "_sha1",
- "_sha256",
- "_sha3",
- "_sha512",
- "_signal",
- "_sitebuiltins",
- "_socket",
- "_sqlite3",
- "_sre",
- "_ssl",
- "_stat",
- "_string",
- "_strptime",
- "_struct",
- "_symtable",
- "_thread",
- "_threading_local",
- "_tkinter",
- "_tracemalloc",
- "_uuid",
- "_warnings",
- "_weakref",
- "_weakrefset",
- "_winapi",
- "abc",
- "aifc",
- "antigravity",
- "argparse",
- "array",
- "ast",
- "asynchat",
- "asyncio",
- "asyncore",
- "atexit",
- "audioop",
- "base64",
- "bdb",
- "binascii",
- "binhex",
- "bisect",
- "builtins",
- "bz2",
- "cProfile",
- "calendar",
- "cgi",
- "cgitb",
- "chunk",
- "cmath",
- "cmd",
- "code",
- "codecs",
- "codeop",
- "collections",
- "colorsys",
- "compileall",
- "concurrent",
- "configparser",
- "contextlib",
- "contextvars",
- "copy",
- "copyreg",
- "crypt",
- "csv",
- "ctypes",
- "curses",
- "dataclasses",
- "datetime",
- "dbm",
- "decimal",
- "difflib",
- "dis",
- "distutils",
- "doctest",
- "dummy_threading",
- "email",
- "encodings",
- "ensurepip",
- "enum",
- "errno",
- "faulthandler",
- "fcntl",
- "filecmp",
- "fileinput",
- "fnmatch",
- "formatter",
- "fractions",
- "ftplib",
- "functools",
- "gc",
- "genericpath",
- "getopt",
- "getpass",
- "gettext",
- "glob",
- "grp",
- "gzip",
- "hashlib",
- "heapq",
- "hmac",
- "html",
- "http",
- "idlelib",
- "imaplib",
- "imghdr",
- "imp",
- "importlib",
- "inspect",
- "io",
- "ipaddress",
- "itertools",
- "json",
- "keyword",
- "lib2to3",
- "linecache",
- "locale",
- "logging",
- "lzma",
- "macpath",
- "mailbox",
- "mailcap",
- "marshal",
- "math",
- "mimetypes",
- "mmap",
- "modulefinder",
- "msilib",
- "msvcrt",
- "multiprocessing",
- "netrc",
- "nis",
- "nntplib",
- "nt",
- "ntpath",
- "nturl2path",
- "numbers",
- "opcode",
- "operator",
- "optparse",
- "os",
- "ossaudiodev",
- "parser",
- "pathlib",
- "pdb",
- "pickle",
- "pickletools",
- "pipes",
- "pkgutil",
- "platform",
- "plistlib",
- "poplib",
- "posix",
- "posixpath",
- "pprint",
- "profile",
- "pstats",
- "pty",
- "pwd",
- "py_compile",
- "pyclbr",
- "pydoc",
- "pydoc_data",
- "pyexpat",
- "queue",
- "quopri",
- "random",
- "re",
- "readline",
- "reprlib",
- "resource",
- "rlcompleter",
- "runpy",
- "sched",
- "secrets",
- "select",
- "selectors",
- "shelve",
- "shlex",
- "shutil",
- "signal",
- "site",
- "smtpd",
- "smtplib",
- "sndhdr",
- "socket",
- "socketserver",
- "spwd",
- "sqlite3",
- "sre_compile",
- "sre_constants",
- "sre_parse",
- "ssl",
- "stat",
- "statistics",
- "string",
- "stringprep",
- "struct",
- "subprocess",
- "sunau",
- "symbol",
- "symtable",
- "sys",
- "sysconfig",
- "syslog",
- "tabnanny",
- "tarfile",
- "telnetlib",
- "tempfile",
- "termios",
- "textwrap",
- "this",
- "threading",
- "time",
- "timeit",
- "tkinter",
- "token",
- "tokenize",
- "trace",
- "traceback",
- "tracemalloc",
- "tty",
- "turtle",
- "turtledemo",
- "types",
- "typing",
- "unicodedata",
- "unittest",
- "urllib",
- "uu",
- "uuid",
- "venv",
- "warnings",
- "wave",
- "weakref",
- "webbrowser",
- "winreg",
- "winsound",
- "wsgiref",
- "xdrlib",
- "xml",
- "xmlrpc",
- "zipapp",
- "zipfile",
- "zipimport",
- "zlib",
- }
-)
-
-PY_3_8 = frozenset(
- PY_3_7
- - {
- "macpath",
- }
- | {
- "_posixshmem",
- "_statistics",
- "_xxsubinterpreters",
- }
-)
-
-PY_3_9 = frozenset(
- PY_3_8
- - {
- "_dummy_thread",
- "dummy_threading",
- }
- | {
- "_aix_support",
- "_bootsubprocess",
- "_peg_parser",
- "_zoneinfo",
- "graphlib",
- "zoneinfo",
- }
-)
-
-if sys.version_info[:2] == (3, 9):
- stdlib_module_names = PY_3_9
-else:
- raise AssertionError("This module is only intended as a backport for Python <= 3.9")
diff --git a/astroid/bases.py b/astroid/bases.py
index d91a4c9..b7fd80c 100644
--- a/astroid/bases.py
+++ b/astroid/bases.py
@@ -5,6 +5,7 @@
"""This module contains base classes and functions for the nodes and some
inference utils.
"""
+
from __future__ import annotations
import collections
@@ -13,7 +14,7 @@
from typing import TYPE_CHECKING, Any, Literal
from astroid import decorators, nodes
-from astroid.const import PY310_PLUS
+from astroid.const import PY311_PLUS
from astroid.context import (
CallContext,
InferenceContext,
@@ -38,8 +39,9 @@
from astroid.constraint import Constraint
-PROPERTIES = {"builtins.property", "abc.abstractproperty"}
-if PY310_PLUS:
+PROPERTIES = {"builtins.property", "abc.abstractproperty", "functools.cached_property"}
+# enum.property was added in Python 3.11
+if PY311_PLUS:
PROPERTIES.add("enum.property")
# List of possible property names. We use this list in order
@@ -79,24 +81,30 @@
if any(name in stripped for name in POSSIBLE_PROPERTIES):
return True
- # Lookup for subclasses of *property*
if not meth.decorators:
return False
+ # Lookup for subclasses of *property*
for decorator in meth.decorators.nodes or ():
inferred = safe_infer(decorator, context=context)
if inferred is None or isinstance(inferred, UninferableBase):
continue
if isinstance(inferred, nodes.ClassDef):
+ # Check for a class which inherits from a standard property type
+ if any(inferred.is_subtype_of(pclass) for pclass in PROPERTIES):
+ return True
for base_class in inferred.bases:
- if not isinstance(base_class, nodes.Name):
+ # Check for a class which inherits from functools.cached_property
+ # and includes a subscripted type annotation
+ if isinstance(base_class, nodes.Subscript):
+ value = safe_infer(base_class.value, context=context)
+ if not isinstance(value, nodes.ClassDef):
+ continue
+ if value.name != "cached_property":
+ continue
+ module, _ = value.lookup(value.name)
+ if isinstance(module, nodes.Module) and module.name == "functools":
+ return True
continue
- module, _ = base_class.lookup(base_class.name)
- if (
- isinstance(module, nodes.Module)
- and module.name == "builtins"
- and base_class.name == "property"
- ):
- return True
return False
@@ -243,7 +251,9 @@
values = self._proxied.instance_attr(name, context)
except AttributeInferenceError as exc:
if self.special_attributes and name in self.special_attributes:
- return [self.special_attributes.lookup(name)]
+ special_attr = self.special_attributes.lookup(name)
+ if not isinstance(special_attr, nodes.Unknown):
+ return [special_attr]
if lookupclass:
# Class attributes not available through the instance
@@ -452,14 +462,18 @@
def getattr(self, name: str, context: InferenceContext | None = None):
if name in self.special_attributes:
- return [self.special_attributes.lookup(name)]
+ special_attr = self.special_attributes.lookup(name)
+ if not isinstance(special_attr, nodes.Unknown):
+ return [special_attr]
return self._proxied.getattr(name, context)
def igetattr(
self, name: str, context: InferenceContext | None = None
) -> Iterator[InferenceResult]:
if name in self.special_attributes:
- return iter((self.special_attributes.lookup(name),))
+ special_attr = self.special_attributes.lookup(name)
+ if not isinstance(special_attr, nodes.Unknown):
+ return iter((special_attr,))
return self._proxied.igetattr(name, context)
def infer_call_result(
@@ -532,9 +546,13 @@
self,
proxy: nodes.FunctionDef | nodes.Lambda | UnboundMethod,
bound: SuccessfulInferenceResult,
+ original_caller: SuccessfulInferenceResult | None = None,
) -> None:
super().__init__(proxy)
self.bound = bound
+ # For super() calls: the actual instance/class that called super()
+ # Used to correctly infer return type of methods returning Self
+ self.original_caller = original_caller
def implicit_parameters(self) -> Literal[0, 1]:
if self.name == "__new__":
@@ -564,10 +582,14 @@
raise InferenceError(context=context) from e
if not isinstance(mcs, nodes.ClassDef):
# Not a valid first argument.
- return None
+ raise InferenceError(
+ "type.__new__() requires a class for metaclass", context=context
+ )
if not mcs.is_subtype_of("builtins.type"):
# Not a valid metaclass.
- return None
+ raise InferenceError(
+ "type.__new__() metaclass must be a subclass of type", context=context
+ )
# Verify the name
try:
@@ -576,10 +598,14 @@
raise InferenceError(context=context) from e
if not isinstance(name, nodes.Const):
# Not a valid name, needs to be a const.
- return None
+ raise InferenceError(
+ "type.__new__() requires a constant for name", context=context
+ )
if not isinstance(name.value, str):
# Needs to be a string.
- return None
+ raise InferenceError(
+ "type.__new__() requires a string for name", context=context
+ )
# Verify the bases
try:
@@ -588,14 +614,18 @@
raise InferenceError(context=context) from e
if not isinstance(bases, nodes.Tuple):
# Needs to be a tuple.
- return None
+ raise InferenceError(
+ "type.__new__() requires a tuple for bases", context=context
+ )
try:
inferred_bases = [next(elt.infer(context=context)) for elt in bases.elts]
except StopIteration as e:
raise InferenceError(context=context) from e
if any(not isinstance(base, nodes.ClassDef) for base in inferred_bases):
# All the bases needs to be Classes
- return None
+ raise InferenceError(
+ "type.__new__() requires classes for bases", context=context
+ )
# Verify the attributes.
try:
@@ -604,7 +634,9 @@
raise InferenceError(context=context) from e
if not isinstance(attrs, nodes.Dict):
# Needs to be a dictionary.
- return None
+ raise InferenceError(
+ "type.__new__() requires a dict for attrs", context=context
+ )
cls_locals: dict[str, list[InferenceResult]] = collections.defaultdict(list)
for key, value in attrs.items:
try:
@@ -651,15 +683,21 @@
caller: SuccessfulInferenceResult | None,
context: InferenceContext | None = None,
) -> Iterator[InferenceResult]:
- context = bind_context_to_node(context, self.bound)
+ # For super() calls, use original_caller to correctly infer Self return type
+ bound_node = self.original_caller if self.original_caller else self.bound
+ context = bind_context_to_node(context, bound_node)
if (
isinstance(self.bound, nodes.ClassDef)
and self.bound.name == "type"
and self.name == "__new__"
and isinstance(caller, nodes.Call)
- and len(caller.args) == 4
):
# Check if we have a ``type.__new__(mcs, name, bases, attrs)`` call.
+ if len(caller.args) != 4:
+ raise InferenceError(
+ f"type.__new__() requires 4 arguments, got {len(caller.args)}",
+ context=context,
+ )
new_cls = self._infer_type_new_call(caller, context)
if new_cls:
return iter((new_cls,))
diff --git a/astroid/brain/brain_attrs.py b/astroid/brain/brain_attrs.py
index 23ec9f6..9d7e7b5 100644
--- a/astroid/brain/brain_attrs.py
+++ b/astroid/brain/brain_attrs.py
@@ -8,9 +8,10 @@
Without this hook pylint reports unsupported-assignment-operation
for attrs classes
"""
+
+from astroid import nodes
+from astroid.brain.helpers import is_class_var
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import AnnAssign, Assign, AssignName, Call, Unknown
-from astroid.nodes.scoped_nodes import ClassDef
from astroid.util import safe_infer
ATTRIB_NAMES = frozenset(
@@ -50,7 +51,7 @@
if not node.decorators:
return False
for decorator_attribute in node.decorators.nodes:
- if isinstance(decorator_attribute, Call): # decorator with arguments
+ if isinstance(decorator_attribute, nodes.Call): # decorator with arguments
decorator_attribute = decorator_attribute.func
if decorator_attribute.as_string() in decorator_names:
return True
@@ -61,35 +62,42 @@
return False
-def attr_attributes_transform(node: ClassDef) -> None:
+def attr_attributes_transform(node: nodes.ClassDef) -> None:
"""Given that the ClassNode has an attr decorator,
rewrite class attributes as instance attributes
"""
# Astroid can't infer this attribute properly
# Prevents https://github.com/pylint-dev/pylint/issues/1884
- node.locals["__attrs_attrs__"] = [Unknown(parent=node)]
+ node.locals["__attrs_attrs__"] = [nodes.Unknown(parent=node)]
use_bare_annotations = is_decorated_with_attrs(node, NEW_ATTRS_NAMES)
for cdef_body_node in node.body:
- if not isinstance(cdef_body_node, (Assign, AnnAssign)):
+ if not isinstance(cdef_body_node, (nodes.Assign, nodes.AnnAssign)):
continue
- if isinstance(cdef_body_node.value, Call):
+ if isinstance(cdef_body_node.value, nodes.Call):
if cdef_body_node.value.func.as_string() not in ATTRIB_NAMES:
continue
elif not use_bare_annotations:
continue
+
+ # Skip attributes that are explicitly annotated as class variables
+ if isinstance(cdef_body_node, nodes.AnnAssign) and is_class_var(
+ cdef_body_node.annotation
+ ):
+ continue
+
targets = (
cdef_body_node.targets
if hasattr(cdef_body_node, "targets")
else [cdef_body_node.target]
)
for target in targets:
- rhs_node = Unknown(
+ rhs_node = nodes.Unknown(
lineno=cdef_body_node.lineno,
col_offset=cdef_body_node.col_offset,
parent=cdef_body_node,
)
- if isinstance(target, AssignName):
+ if isinstance(target, nodes.AssignName):
# Could be a subscript if the code analysed is
# i = Optional[str] = ""
# See https://github.com/pylint-dev/pylint/issues/4439
@@ -99,5 +107,5 @@
def register(manager: AstroidManager) -> None:
manager.register_transform(
- ClassDef, attr_attributes_transform, is_decorated_with_attrs
+ nodes.ClassDef, attr_attributes_transform, is_decorated_with_attrs
)
diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py
index a56b152..a2ca955 100644
--- a/astroid/brain/brain_builtin_inference.py
+++ b/astroid/brain/brain_builtin_inference.py
@@ -9,7 +9,7 @@
import itertools
from collections.abc import Callable, Iterable, Iterator
from functools import partial
-from typing import TYPE_CHECKING, Any, NoReturn, Union, cast
+from typing import TYPE_CHECKING, Any, NoReturn, cast
from astroid import arguments, helpers, nodes, objects, util
from astroid.builder import AstroidBuilder
@@ -33,26 +33,13 @@
if TYPE_CHECKING:
from astroid.bases import Instance
-ContainerObjects = Union[
- objects.FrozenSet,
- objects.DictItems,
- objects.DictKeys,
- objects.DictValues,
-]
+ContainerObjects = (
+ objects.FrozenSet | objects.DictItems | objects.DictKeys | objects.DictValues
+)
-BuiltContainers = Union[
- type[tuple],
- type[list],
- type[set],
- type[frozenset],
-]
+BuiltContainers = type[tuple] | type[list] | type[set] | type[frozenset]
-CopyResult = Union[
- nodes.Dict,
- nodes.List,
- nodes.Set,
- objects.FrozenSet,
-]
+CopyResult = nodes.Dict | nodes.List | nodes.Set | objects.FrozenSet
OBJECT_DUNDER_NEW = "object.__new__"
@@ -282,7 +269,7 @@
if isinstance(arg, klass):
return arg
if isinstance(arg, iterables):
- arg = cast(Union[nodes.BaseContainer, ContainerObjects], arg)
+ arg = cast((nodes.BaseContainer | ContainerObjects), arg)
if all(isinstance(elt, nodes.Const) for elt in arg.elts):
elts = [cast(nodes.Const, elt).value for elt in arg.elts]
else:
@@ -769,12 +756,14 @@
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc
if not isinstance(obj_type, nodes.ClassDef):
- raise UseInferenceDefault("TypeError: arg 1 must be class")
+ raise UseInferenceDefault(
+ f"TypeError: arg 1 must be class, not {type(obj_type)!r}"
+ )
# The right hand argument is the class(es) that the given
# object is to be checked against.
try:
- class_container = _class_or_tuple_to_container(
+ class_container = helpers.class_or_tuple_to_container(
class_or_tuple_node, context=context
)
except InferenceError as exc:
@@ -809,7 +798,7 @@
# The right hand argument is the class(es) that the given
# obj is to be check is an instance of
try:
- class_container = _class_or_tuple_to_container(
+ class_container = helpers.class_or_tuple_to_container(
class_or_tuple_node, context=context
)
except InferenceError as exc:
@@ -825,30 +814,6 @@
return nodes.Const(isinstance_bool)
-def _class_or_tuple_to_container(
- node: InferenceResult, context: InferenceContext | None = None
-) -> list[InferenceResult]:
- # Move inferences results into container
- # to simplify later logic
- # raises InferenceError if any of the inferences fall through
- try:
- node_infer = next(node.infer(context=context))
- except StopIteration as e:
- raise InferenceError(node=node, context=context) from e
- # arg2 MUST be a type or a TUPLE of types
- # for isinstance
- if isinstance(node_infer, nodes.Tuple):
- try:
- class_container = [
- next(node.infer(context=context)) for node in node_infer.elts
- ]
- except StopIteration as e:
- raise InferenceError(node=node, context=context) from e
- else:
- class_container = [node_infer]
- return class_container
-
-
def infer_len(node, context: InferenceContext | None = None) -> nodes.Const:
"""Infer length calls.
@@ -1009,7 +974,7 @@
def _is_str_format_call(node: nodes.Call) -> bool:
"""Catch calls to str.format()."""
- if not isinstance(node.func, nodes.Attribute) or not node.func.attrname == "format":
+ if not (isinstance(node.func, nodes.Attribute) and node.func.attrname == "format"):
return False
if isinstance(node.func.expr, nodes.Name):
@@ -1029,8 +994,9 @@
value: nodes.Const
if isinstance(node.func.expr, nodes.Name):
- if not (inferred := util.safe_infer(node.func.expr)) or not isinstance(
- inferred, nodes.Const
+ if not (
+ (inferred := util.safe_infer(node.func.expr))
+ and isinstance(inferred, nodes.Const)
):
return iter([util.Uninferable])
value = inferred
diff --git a/astroid/brain/brain_collections.py b/astroid/brain/brain_collections.py
index 94944e6..393bc60 100644
--- a/astroid/brain/brain_collections.py
+++ b/astroid/brain/brain_collections.py
@@ -19,17 +19,13 @@
def _collections_transform():
- return parse(
- """
+ return parse("""
class defaultdict(dict):
default_factory = None
def __missing__(self, key): pass
def __getitem__(self, key): return default_factory
- """
- + _deque_mock()
- + _ordered_dict_mock()
- )
+ """ + _deque_mock() + _ordered_dict_mock())
def _collections_abc_313_transform() -> nodes.Module:
diff --git a/astroid/brain/brain_crypt.py b/astroid/brain/brain_crypt.py
index 71f9dfc..92d30b9 100644
--- a/astroid/brain/brain_crypt.py
+++ b/astroid/brain/brain_crypt.py
@@ -9,8 +9,7 @@
def _re_transform() -> nodes.Module:
- return parse(
- """
+ return parse("""
from collections import namedtuple
_Method = namedtuple('_Method', 'name ident salt_chars total_size')
@@ -19,8 +18,7 @@
METHOD_BLOWFISH = _Method('BLOWFISH', 2, 'b', 22)
METHOD_MD5 = _Method('MD5', '1', 8, 34)
METHOD_CRYPT = _Method('CRYPT', None, 2, 13)
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_ctypes.py b/astroid/brain/brain_ctypes.py
index 8ae10bc..9d2e163 100644
--- a/astroid/brain/brain_ctypes.py
+++ b/astroid/brain/brain_ctypes.py
@@ -10,6 +10,7 @@
Thus astroid doesn't know that the value member is a builtin type
among float, int, bytes or str.
"""
+
import sys
from astroid import nodes
@@ -56,26 +57,22 @@
("c_wchar", "str", "u"),
)
- src = [
- """
+ src = ["""
from _ctypes import _SimpleCData
class c_bool(_SimpleCData):
def __init__(self, value):
self.value = True
self._type_ = '?'
- """
- ]
+ """]
for c_type, builtin_type, type_code in c_class_to_type:
- src.append(
- f"""
+ src.append(f"""
class {c_type}(_SimpleCData):
def __init__(self, value):
self.value = {builtin_type}(value)
self._type_ = '{type_code}'
- """
- )
+ """)
return parse("\n".join(src))
diff --git a/astroid/brain/brain_curses.py b/astroid/brain/brain_curses.py
index 5824fd7..5055ec4 100644
--- a/astroid/brain/brain_curses.py
+++ b/astroid/brain/brain_curses.py
@@ -9,8 +9,7 @@
def _curses_transform() -> nodes.Module:
- return parse(
- """
+ return parse("""
A_ALTCHARSET = 1
A_BLINK = 1
A_BOLD = 1
@@ -177,8 +176,7 @@
COLOR_RED = 1
COLOR_WHITE = 1
COLOR_YELLOW = 1
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_dataclasses.py b/astroid/brain/brain_dataclasses.py
index 92d983e..b6b1956 100644
--- a/astroid/brain/brain_dataclasses.py
+++ b/astroid/brain/brain_dataclasses.py
@@ -15,22 +15,28 @@
from __future__ import annotations
from collections.abc import Iterator
-from typing import Literal, Union
+from typing import Literal
from astroid import bases, context, nodes
+from astroid.brain.helpers import is_class_var
from astroid.builder import parse
-from astroid.const import PY310_PLUS, PY313_PLUS
-from astroid.exceptions import AstroidSyntaxError, InferenceError, UseInferenceDefault
+from astroid.const import PY313_PLUS
+from astroid.exceptions import (
+ AstroidSyntaxError,
+ InferenceError,
+ MroError,
+ UseInferenceDefault,
+)
from astroid.inference_tip import inference_tip
from astroid.manager import AstroidManager
from astroid.typing import InferenceResult
from astroid.util import Uninferable, UninferableBase, safe_infer
-_FieldDefaultReturn = Union[
- None,
- tuple[Literal["default"], nodes.NodeNG],
- tuple[Literal["default_factory"], nodes.Call],
-]
+_FieldDefaultReturn = (
+ None
+ | tuple[Literal["default"], nodes.NodeNG]
+ | tuple[Literal["default_factory"], nodes.Call]
+)
DATACLASSES_DECORATORS = frozenset(("dataclass",))
FIELD_NAME = "field"
@@ -44,7 +50,7 @@
node: nodes.ClassDef, decorator_names: frozenset[str] = DATACLASSES_DECORATORS
) -> bool:
"""Return True if a decorated node has a `dataclass` decorator applied."""
- if not isinstance(node, nodes.ClassDef) or not node.decorators:
+ if not (isinstance(node, nodes.ClassDef) and node.decorators):
return False
return any(
@@ -53,7 +59,7 @@
)
-def dataclass_transform(node: nodes.ClassDef) -> None:
+def dataclass_transform(node: nodes.ClassDef) -> nodes.ClassDef | None:
"""Rewrite a dataclass to be easily understood by pylint."""
node.is_dataclass = True
@@ -69,17 +75,17 @@
node.instance_attrs[name] = [rhs_node]
if not _check_generate_dataclass_init(node):
- return
+ return None
kw_only_decorated = False
- if PY310_PLUS and node.decorators.nodes:
+ if node.decorators.nodes:
for decorator in node.decorators.nodes:
if not isinstance(decorator, nodes.Call):
kw_only_decorated = False
break
for keyword in decorator.keywords:
if keyword.arg == "kw_only":
- kw_only_decorated = keyword.value.bool_value()
+ kw_only_decorated = keyword.value.bool_value() is True
init_str = _generate_dataclass_init(
node,
@@ -101,6 +107,7 @@
new_assign = parse(f"{DEFAULT_FACTORY} = object()").body[0]
new_assign.parent = root
root.locals[DEFAULT_FACTORY] = [new_assign.targets[0]]
+ return node
def _get_dataclass_attributes(
@@ -111,13 +118,14 @@
If init is True, also include InitVars.
"""
for assign_node in node.body:
- if not isinstance(assign_node, nodes.AnnAssign) or not isinstance(
- assign_node.target, nodes.AssignName
+ if not (
+ isinstance(assign_node, nodes.AnnAssign)
+ and isinstance(assign_node.target, nodes.AssignName)
):
continue
# Annotation is never None
- if _is_class_var(assign_node.annotation): # type: ignore[arg-type]
+ if is_class_var(assign_node.annotation): # type: ignore[arg-type]
continue
if _is_keyword_only_sentinel(assign_node.annotation):
@@ -155,7 +163,7 @@
# Check for keyword arguments of the form init=False
return not any(
keyword.arg == "init"
- and not keyword.value.bool_value() # type: ignore[union-attr] # value is never None
+ and keyword.value.bool_value() is False # type: ignore[union-attr] # value is never None
for keyword in found.keywords
)
@@ -171,7 +179,12 @@
# See TODO down below
# all_have_defaults = True
- for base in reversed(node.mro()):
+ try:
+ mro = node.mro()
+ except MroError:
+ return pos_only_store, kw_only_store
+
+ for base in reversed(mro):
if not base.is_dataclass:
continue
try:
@@ -221,7 +234,12 @@
def _get_previous_field_default(node: nodes.ClassDef, name: str) -> nodes.NodeNG | None:
"""Get the default value of a previously defined field."""
- for base in reversed(node.mro()):
+ try:
+ mro = node.mro()
+ except MroError:
+ return None
+
+ for base in reversed(mro):
if not base.is_dataclass:
continue
if name in base.locals:
@@ -271,7 +289,7 @@
if is_field:
# Skip any fields that have `init=False`
if any(
- keyword.arg == "init" and not keyword.value.bool_value()
+ keyword.arg == "init" and (keyword.value.bool_value() is False)
for keyword in value.keywords # type: ignore[union-attr] # value is never None
):
# Also remove the name from the previous arguments to be inserted later
@@ -341,7 +359,7 @@
if is_field:
kw_only = [k for k in value.keywords if k.arg == "kw_only"] # type: ignore[union-attr]
if kw_only:
- if kw_only[0].value.bool_value():
+ if kw_only[0].value.bool_value() is True:
kw_only_params.append(param_str)
else:
params.append(param_str)
@@ -550,20 +568,8 @@
return None
-def _is_class_var(node: nodes.NodeNG) -> bool:
- """Return True if node is a ClassVar, with or without subscripting."""
- try:
- inferred = next(node.infer())
- except (InferenceError, StopIteration):
- return False
-
- return getattr(inferred, "name", "") == "ClassVar"
-
-
def _is_keyword_only_sentinel(node: nodes.NodeNG) -> bool:
"""Return True if node is the KW_ONLY sentinel."""
- if not PY310_PLUS:
- return False
inferred = safe_infer(node)
return (
isinstance(inferred, bases.Instance)
diff --git a/astroid/brain/brain_dateutil.py b/astroid/brain/brain_dateutil.py
index c27343f..b6452d2 100644
--- a/astroid/brain/brain_dateutil.py
+++ b/astroid/brain/brain_dateutil.py
@@ -13,15 +13,11 @@
def dateutil_transform() -> nodes.Module:
- return AstroidBuilder(AstroidManager()).string_build(
- textwrap.dedent(
- """
+ return AstroidBuilder(AstroidManager()).string_build(textwrap.dedent("""
import datetime
def parse(timestr, parserinfo=None, **kwargs):
return datetime.datetime()
- """
- )
- )
+ """))
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_functools.py b/astroid/brain/brain_functools.py
index c11b856..4af3d4f 100644
--- a/astroid/brain/brain_functools.py
+++ b/astroid/brain/brain_functools.py
@@ -17,8 +17,6 @@
from astroid.inference_tip import inference_tip
from astroid.interpreter import objectmodel
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import AssignName, Attribute, Call, Name
-from astroid.nodes.scoped_nodes import FunctionDef
from astroid.typing import InferenceResult, SuccessfulInferenceResult
from astroid.util import UninferableBase, safe_infer
@@ -38,12 +36,10 @@
@property
def attr_cache_info(self):
- cache_info = extract_node(
- """
+ cache_info = extract_node("""
from functools import _CacheInfo
_CacheInfo(0, 0, 0, 0)
- """
- )
+ """)
class CacheInfoBoundMethod(BoundMethod):
def infer_call_result(
@@ -92,7 +88,7 @@
raise UseInferenceDefault from exc
if isinstance(inferred_wrapped_function, UninferableBase):
raise UseInferenceDefault("Cannot infer the wrapped function")
- if not isinstance(inferred_wrapped_function, FunctionDef):
+ if not isinstance(inferred_wrapped_function, nodes.FunctionDef):
raise UseInferenceDefault("The wrapped function is not a function")
# Determine if the passed keywords into the callsite are supported
@@ -106,7 +102,9 @@
inferred_wrapped_function.args.kwonlyargs or (),
)
parameter_names = {
- param.name for param in function_parameters if isinstance(param, AssignName)
+ param.name
+ for param in function_parameters
+ if isinstance(param, nodes.AssignName)
}
if set(call.keyword_arguments) - parameter_names:
raise UseInferenceDefault("wrapped function received unknown parameters")
@@ -135,23 +133,25 @@
if not node.decorators:
return False
for decorator in node.decorators.nodes:
- if not isinstance(decorator, (Attribute, Call)):
+ if not isinstance(decorator, (nodes.Attribute, nodes.Call)):
continue
if _looks_like_functools_member(decorator, "lru_cache"):
return True
return False
-def _looks_like_functools_member(node: Attribute | Call, member: str) -> bool:
+def _looks_like_functools_member(
+ node: nodes.Attribute | nodes.Call, member: str
+) -> bool:
"""Check if the given Call node is the wanted member of functools."""
- if isinstance(node, Attribute):
+ if isinstance(node, nodes.Attribute):
return node.attrname == member
- if isinstance(node.func, Name):
+ if isinstance(node.func, nodes.Name):
return node.func.name == member
- if isinstance(node.func, Attribute):
+ if isinstance(node.func, nodes.Attribute):
return (
node.func.attrname == member
- and isinstance(node.func.expr, Name)
+ and isinstance(node.func.expr, nodes.Name)
and node.func.expr.name == "functools"
)
return False
@@ -161,10 +161,12 @@
def register(manager: AstroidManager) -> None:
- manager.register_transform(FunctionDef, _transform_lru_cache, _looks_like_lru_cache)
+ manager.register_transform(
+ nodes.FunctionDef, _transform_lru_cache, _looks_like_lru_cache
+ )
manager.register_transform(
- Call,
+ nodes.Call,
inference_tip(_functools_partial_inference),
_looks_like_partial,
)
diff --git a/astroid/brain/brain_gi.py b/astroid/brain/brain_gi.py
index fa60077..da2b0db 100644
--- a/astroid/brain/brain_gi.py
+++ b/astroid/brain/brain_gi.py
@@ -54,6 +54,35 @@
)
+def _gi_supports_inspect_signature():
+ """
+ Indicates if pygobject supports inspect.signature().
+ """
+ import gi
+
+ try:
+ # inspect.signature() is supported since pygobject==3.51.0 (ee9558e4).
+ gi.check_version((3, 51, 0))
+ return True
+ except ValueError:
+ pass
+ return False
+
+
+def _gi_is_method_call(obj):
+ if _gi_supports_inspect_signature():
+ # Since inspect.signature() is supported, the workaround to use
+ # inspect.ismethoddescriptor() was disabled and cannot be used anymore
+ # to tell apart functions from methods.
+ # See https://github.com/pylint-dev/astroid/issues/2594
+ try:
+ sig = str(inspect.signature(obj))
+ return sig == "(self)" or sig.startswith("(self, ")
+ except Exception: # pylint: disable=broad-except
+ return False
+ return inspect.ismethod(obj) or inspect.ismethoddescriptor(obj)
+
+
def _gi_build_stub(parent): # noqa: C901
"""
Inspect the passed module recursively and build stubs for functions,
@@ -84,7 +113,7 @@
classes[name] = obj
elif inspect.isfunction(obj) or inspect.isbuiltin(obj):
functions[name] = obj
- elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj):
+ elif _gi_is_method_call(obj):
methods[name] = obj
elif (
str(obj).startswith("<flags")
diff --git a/astroid/brain/brain_http.py b/astroid/brain/brain_http.py
index 3a8a858..da0d65f 100644
--- a/astroid/brain/brain_http.py
+++ b/astroid/brain/brain_http.py
@@ -3,6 +3,7 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Astroid brain hints for some of the `http` module."""
+
import textwrap
from astroid import nodes
@@ -12,12 +13,22 @@
def _http_transform() -> nodes.Module:
- code = textwrap.dedent(
- """
- from enum import IntEnum
+ code = textwrap.dedent("""
+ from enum import IntEnum, StrEnum
from collections import namedtuple
_HTTPStatus = namedtuple('_HTTPStatus', 'value phrase description')
+ class HTTPMethod(StrEnum):
+ GET = "GET"
+ POST = "POST"
+ PUT = "PUT"
+ DELETE = "DELETE"
+ HEAD = "HEAD"
+ OPTIONS = "OPTIONS"
+ PATCH = "PATCH"
+ TRACE = "TRACE"
+ CONNECT = "CONNECT"
+
class HTTPStatus(IntEnum):
@property
@@ -35,6 +46,7 @@
SWITCHING_PROTOCOLS = _HTTPStatus(101, 'Switching Protocols',
'Switching to new protocol; obey Upgrade header')
PROCESSING = _HTTPStatus(102, 'Processing', '')
+ EARLY_HINTS = _HTTPStatus(103, 'Early Hints')
OK = _HTTPStatus(200, 'OK', 'Request fulfilled, document follows')
CREATED = _HTTPStatus(201, 'Created', 'Document created, URL follows')
ACCEPTED = _HTTPStatus(202, 'Accepted',
@@ -87,22 +99,27 @@
'Client must specify Content-Length')
PRECONDITION_FAILED = _HTTPStatus(412, 'Precondition Failed',
'Precondition in headers is false')
- REQUEST_ENTITY_TOO_LARGE = _HTTPStatus(413, 'Request Entity Too Large',
- 'Entity is too large')
- REQUEST_URI_TOO_LONG = _HTTPStatus(414, 'Request-URI Too Long',
- 'URI is too long')
+ CONTENT_TOO_LARGE = _HTTPStatus(413, 'Content Too Large',
+ 'Content is too large')
+ REQUEST_ENTITY_TOO_LARGE = CONTENT_TOO_LARGE
+ URI_TOO_LONG = _HTTPStatus(414, 'URI Too Long', 'URI is too long')
+ REQUEST_URI_TOO_LONG = URI_TOO_LONG
UNSUPPORTED_MEDIA_TYPE = _HTTPStatus(415, 'Unsupported Media Type',
'Entity body in unsupported format')
- REQUESTED_RANGE_NOT_SATISFIABLE = _HTTPStatus(416,
- 'Requested Range Not Satisfiable',
- 'Cannot satisfy request range')
+ RANGE_NOT_SATISFIABLE = (416, 'Range Not Satisfiable',
+ 'Cannot satisfy request range')
+ REQUESTED_RANGE_NOT_SATISFIABLE = RANGE_NOT_SATISFIABLE
EXPECTATION_FAILED = _HTTPStatus(417, 'Expectation Failed',
'Expect condition could not be satisfied')
+ IM_A_TEAPOT = _HTTPStatus(418, 'I\\\'m a Teapot',
+ 'Server refuses to brew coffee because it is a teapot.')
MISDIRECTED_REQUEST = _HTTPStatus(421, 'Misdirected Request',
'Server is not able to produce a response')
- UNPROCESSABLE_ENTITY = _HTTPStatus(422, 'Unprocessable Entity')
+ UNPROCESSABLE_CONTENT = _HTTPStatus(422, 'Unprocessable Content')
+ UNPROCESSABLE_ENTITY = UNPROCESSABLE_CONTENT
LOCKED = _HTTPStatus(423, 'Locked')
FAILED_DEPENDENCY = _HTTPStatus(424, 'Failed Dependency')
+ TOO_EARLY = _HTTPStatus(425, 'Too Early')
UPGRADE_REQUIRED = _HTTPStatus(426, 'Upgrade Required')
PRECONDITION_REQUIRED = _HTTPStatus(428, 'Precondition Required',
'The origin server requires the request to be conditional')
@@ -136,20 +153,18 @@
NETWORK_AUTHENTICATION_REQUIRED = _HTTPStatus(511,
'Network Authentication Required',
'The client needs to authenticate to gain network access')
- """
- )
+ """)
return AstroidBuilder(AstroidManager()).string_build(code)
def _http_client_transform() -> nodes.Module:
- return AstroidBuilder(AstroidManager()).string_build(
- textwrap.dedent(
- """
+ return AstroidBuilder(AstroidManager()).string_build(textwrap.dedent("""
from http import HTTPStatus
CONTINUE = HTTPStatus.CONTINUE
SWITCHING_PROTOCOLS = HTTPStatus.SWITCHING_PROTOCOLS
PROCESSING = HTTPStatus.PROCESSING
+ EARLY_HINTS = HTTPStatus.EARLY_HINTS
OK = HTTPStatus.OK
CREATED = HTTPStatus.CREATED
ACCEPTED = HTTPStatus.ACCEPTED
@@ -181,14 +196,20 @@
GONE = HTTPStatus.GONE
LENGTH_REQUIRED = HTTPStatus.LENGTH_REQUIRED
PRECONDITION_FAILED = HTTPStatus.PRECONDITION_FAILED
- REQUEST_ENTITY_TOO_LARGE = HTTPStatus.REQUEST_ENTITY_TOO_LARGE
- REQUEST_URI_TOO_LONG = HTTPStatus.REQUEST_URI_TOO_LONG
+ CONTENT_TOO_LARGE = HTTPStatus.CONTENT_TOO_LARGE
+ REQUEST_ENTITY_TOO_LARGE = HTTPStatus.CONTENT_TOO_LARGE
+ URI_TOO_LONG = HTTPStatus.URI_TOO_LONG
+ REQUEST_URI_TOO_LONG = HTTPStatus.URI_TOO_LONG
UNSUPPORTED_MEDIA_TYPE = HTTPStatus.UNSUPPORTED_MEDIA_TYPE
- REQUESTED_RANGE_NOT_SATISFIABLE = HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE
+ RANGE_NOT_SATISFIABLE = HTTPStatus.RANGE_NOT_SATISFIABLE
+ REQUESTED_RANGE_NOT_SATISFIABLE = HTTPStatus.RANGE_NOT_SATISFIABLE
EXPECTATION_FAILED = HTTPStatus.EXPECTATION_FAILED
- UNPROCESSABLE_ENTITY = HTTPStatus.UNPROCESSABLE_ENTITY
+ IM_A_TEAPOT = HTTPStatus.IM_A_TEAPOT
+ UNPROCESSABLE_CONTENT = HTTPStatus.UNPROCESSABLE_CONTENT
+ UNPROCESSABLE_ENTITY = HTTPStatus.UNPROCESSABLE_CONTENT
LOCKED = HTTPStatus.LOCKED
FAILED_DEPENDENCY = HTTPStatus.FAILED_DEPENDENCY
+ TOO_EARLY = HTTPStatus.TOO_EARLY
UPGRADE_REQUIRED = HTTPStatus.UPGRADE_REQUIRED
PRECONDITION_REQUIRED = HTTPStatus.PRECONDITION_REQUIRED
TOO_MANY_REQUESTS = HTTPStatus.TOO_MANY_REQUESTS
@@ -204,9 +225,7 @@
LOOP_DETECTED = HTTPStatus.LOOP_DETECTED
NOT_EXTENDED = HTTPStatus.NOT_EXTENDED
NETWORK_AUTHENTICATION_REQUIRED = HTTPStatus.NETWORK_AUTHENTICATION_REQUIRED
- """
- )
- )
+ """))
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_hypothesis.py b/astroid/brain/brain_hypothesis.py
index ba20f06..e656bef 100644
--- a/astroid/brain/brain_hypothesis.py
+++ b/astroid/brain/brain_hypothesis.py
@@ -16,6 +16,7 @@
a_strategy()
"""
+
from astroid.manager import AstroidManager
from astroid.nodes.scoped_nodes import FunctionDef
diff --git a/astroid/brain/brain_io.py b/astroid/brain/brain_io.py
index ab6e607..48fdfdf 100644
--- a/astroid/brain/brain_io.py
+++ b/astroid/brain/brain_io.py
@@ -3,6 +3,7 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Astroid brain hints for some of the _io C objects."""
+
from astroid.manager import AstroidManager
from astroid.nodes import ClassDef
diff --git a/astroid/brain/brain_mechanize.py b/astroid/brain/brain_mechanize.py
index 62cc2d0..c483f25 100644
--- a/astroid/brain/brain_mechanize.py
+++ b/astroid/brain/brain_mechanize.py
@@ -9,8 +9,7 @@
def mechanize_transform() -> nodes.Module:
- return AstroidBuilder(AstroidManager()).string_build(
- """class Browser(object):
+ return AstroidBuilder(AstroidManager()).string_build("""class Browser(object):
def __getattr__(self, name):
return None
@@ -117,8 +116,7 @@
def visit_response(self, response, request=None):
return None
-"""
- )
+""")
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_multiprocessing.py b/astroid/brain/brain_multiprocessing.py
index e6413b0..a553160 100644
--- a/astroid/brain/brain_multiprocessing.py
+++ b/astroid/brain/brain_multiprocessing.py
@@ -11,23 +11,19 @@
def _multiprocessing_transform():
- module = parse(
- """
+ module = parse("""
from multiprocessing.managers import SyncManager
def Manager():
return SyncManager()
- """
- )
+ """)
# Multiprocessing uses a getattr lookup inside contexts,
# in order to get the attributes they need. Since it's extremely
# dynamic, we use this approach to fake it.
- node = parse(
- """
+ node = parse("""
from multiprocessing.context import DefaultContext, BaseContext
default = DefaultContext()
base = BaseContext()
- """
- )
+ """)
try:
context = next(node["default"].infer())
base = next(node["base"].infer())
@@ -49,8 +45,7 @@
def _multiprocessing_managers_transform():
- return parse(
- """
+ return parse("""
import array
import threading
import multiprocessing.pool as pool
@@ -95,8 +90,7 @@
pass
def shutdown(self):
pass
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py
index 104bd97..ccefb19 100644
--- a/astroid/brain/brain_namedtuple_enum.py
+++ b/astroid/brain/brain_namedtuple_enum.py
@@ -12,7 +12,6 @@
from textwrap import dedent
from typing import Final
-import astroid
from astroid import arguments, bases, nodes, util
from astroid.builder import AstroidBuilder, _extract_single_node, extract_node
from astroid.context import InferenceContext
@@ -204,9 +203,10 @@
)
assert isinstance(func, nodes.NodeNG)
try:
- rename = next(
+ rename_arg_bool_value = next(
call_site.infer_argument(func, "rename", context or InferenceContext())
).bool_value()
+ rename = rename_arg_bool_value is True
except (InferenceError, StopIteration):
rename = False
@@ -226,8 +226,7 @@
field_def.format(name=name, index=index)
for index, name in enumerate(attributes)
)
- fake = AstroidBuilder(AstroidManager()).string_build(
- f"""
+ fake = AstroidBuilder(AstroidManager()).string_build(f"""
class {name}(tuple):
__slots__ = ()
_fields = {attributes!r}
@@ -241,8 +240,7 @@
def __getnewargs__(self):
return tuple(self)
{field_defs}
- """
- )
+ """)
class_node.locals["_asdict"] = fake.body[0].locals["_asdict"]
class_node.locals["_make"] = fake.body[0].locals["_make"]
class_node.locals["_replace"] = fake.body[0].locals["_replace"]
@@ -280,7 +278,9 @@
# <snippet>
for name in (typename, *attributes):
if not isinstance(name, str):
- raise AstroidTypeError("Type names and field names must be strings")
+ raise AstroidTypeError(
+ f"Type names and field names must be strings, not {type(name)!r}"
+ )
if not name.isidentifier():
raise AstroidValueError(
"Type names and field names must be valid" + f"identifiers: {name!r}"
@@ -320,8 +320,7 @@
):
raise UseInferenceDefault
- enum_meta = _extract_single_node(
- """
+ enum_meta = _extract_single_node("""
class EnumMeta(object):
'docstring'
def __call__(self, node):
@@ -352,8 +351,7 @@
return Value()
__members__ = ['']
- """
- )
+ """)
# FIXME arguably, the base here shouldn't be the EnumMeta class definition
# itself, but a reference (Name) to it. Otherwise, the invariant that all
@@ -515,8 +513,7 @@
# For "value", we have no idea what that should be, but for "name", we at least
# know that it should be a string, so infer that as a guess.
if "name" not in target_names:
- code = dedent(
- '''
+ code = dedent('''
@property
def name(self):
"""The name of the Enum member.
@@ -525,8 +522,7 @@
know 'name' should be a string, so this is astroid's best guess.
"""
return ''
- '''
- )
+ ''')
name_dynamicclassattr = AstroidBuilder(AstroidManager()).string_build(code)[
"name"
]
@@ -543,12 +539,10 @@
for annassign in class_node.body
if isinstance(annassign, nodes.AnnAssign)
]
- code = dedent(
- """
+ code = dedent("""
from collections import namedtuple
namedtuple({typename!r}, {fields!r})
- """
- ).format(typename=class_node.name, fields=",".join(annassigns_fields))
+ """).format(typename=class_node.name, fields=",".join(annassigns_fields))
node = extract_node(code)
try:
generated_class_node = next(infer_named_tuple(node, context))
@@ -574,12 +568,10 @@
The class NamedTuple is build dynamically through a call to `type` during
initialization of the `_NamedTuple` variable.
"""
- klass = extract_node(
- """
+ klass = extract_node("""
from typing import _NamedTuple
_NamedTuple
- """
- )
+ """)
return klass.infer(context)
@@ -648,7 +640,7 @@
return field_names
-def _is_enum_subclass(cls: astroid.ClassDef) -> bool:
+def _is_enum_subclass(cls: nodes.ClassDef) -> bool:
"""Return whether cls is a subclass of an Enum."""
return cls.is_subtype_of("enum.Enum")
diff --git a/astroid/brain/brain_nose.py b/astroid/brain/brain_nose.py
deleted file mode 100644
index b194414..0000000
--- a/astroid/brain/brain_nose.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
-# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
-# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
-
-"""Hooks for nose library."""
-
-import re
-import textwrap
-
-from astroid.bases import BoundMethod
-from astroid.brain.helpers import register_module_extender
-from astroid.builder import AstroidBuilder
-from astroid.exceptions import InferenceError
-from astroid.manager import AstroidManager
-from astroid.nodes import List, Module
-
-CAPITALS = re.compile("([A-Z])")
-
-
-def _pep8(name, caps=CAPITALS):
- return caps.sub(lambda m: "_" + m.groups()[0].lower(), name)
-
-
-def _nose_tools_functions():
- """Get an iterator of names and bound methods."""
- module = AstroidBuilder(AstroidManager()).string_build(
- textwrap.dedent(
- """
- import unittest
-
- class Test(unittest.TestCase):
- pass
- a = Test()
- """
- )
- )
- try:
- case = next(module["a"].infer())
- except (InferenceError, StopIteration):
- return
- for method in case.methods():
- if method.name.startswith("assert") and "_" not in method.name:
- pep8_name = _pep8(method.name)
- yield pep8_name, BoundMethod(method, case)
- if method.name == "assertEqual":
- # nose also exports assert_equals.
- yield "assert_equals", BoundMethod(method, case)
-
-
-def _nose_tools_transform(node):
- for method_name, method in _nose_tools_functions():
- node.locals[method_name] = [method]
-
-
-def _nose_tools_trivial_transform():
- """Custom transform for the nose.tools module."""
- stub = AstroidBuilder(AstroidManager()).string_build("""__all__ = []""")
- all_entries = ["ok_", "eq_"]
-
- for pep8_name, method in _nose_tools_functions():
- all_entries.append(pep8_name)
- stub[pep8_name] = method
-
- # Update the __all__ variable, since nose.tools
- # does this manually with .append.
- all_assign = stub["__all__"].parent
- all_object = List(all_entries)
- all_object.parent = all_assign
- all_assign.value = all_object
- return stub
-
-
-def register(manager: AstroidManager) -> None:
- register_module_extender(
- manager, "nose.tools.trivial", _nose_tools_trivial_transform
- )
- manager.register_transform(
- Module, _nose_tools_transform, lambda n: n.name == "nose.tools"
- )
diff --git a/astroid/brain/brain_numpy_core_einsumfunc.py b/astroid/brain/brain_numpy_core_einsumfunc.py
index b72369c..514ba83 100644
--- a/astroid/brain/brain_numpy_core_einsumfunc.py
+++ b/astroid/brain/brain_numpy_core_einsumfunc.py
@@ -14,12 +14,10 @@
def numpy_core_einsumfunc_transform() -> nodes.Module:
- return parse(
- """
+ return parse("""
def einsum(*operands, out=None, optimize=False, **kwargs):
return numpy.ndarray([0, 0])
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_numpy_core_fromnumeric.py b/astroid/brain/brain_numpy_core_fromnumeric.py
index ce4173c..527700e 100644
--- a/astroid/brain/brain_numpy_core_fromnumeric.py
+++ b/astroid/brain/brain_numpy_core_fromnumeric.py
@@ -3,6 +3,7 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Astroid hooks for numpy.core.fromnumeric module."""
+
from astroid import nodes
from astroid.brain.helpers import register_module_extender
from astroid.builder import parse
@@ -10,12 +11,10 @@
def numpy_core_fromnumeric_transform() -> nodes.Module:
- return parse(
- """
+ return parse("""
def sum(a, axis=None, dtype=None, out=None, keepdims=None, initial=None):
return numpy.ndarray([0, 0])
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_numpy_core_function_base.py b/astroid/brain/brain_numpy_core_function_base.py
index 2bfe970..b66ba5f 100644
--- a/astroid/brain/brain_numpy_core_function_base.py
+++ b/astroid/brain/brain_numpy_core_function_base.py
@@ -6,13 +6,13 @@
import functools
+from astroid import nodes
from astroid.brain.brain_numpy_utils import (
attribute_name_looks_like_numpy_member,
infer_numpy_attribute,
)
from astroid.inference_tip import inference_tip
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import Attribute
METHODS_TO_BE_INFERRED = {
"linspace": """def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0):
@@ -26,7 +26,7 @@
def register(manager: AstroidManager) -> None:
manager.register_transform(
- Attribute,
+ nodes.Attribute,
inference_tip(functools.partial(infer_numpy_attribute, METHODS_TO_BE_INFERRED)),
functools.partial(
attribute_name_looks_like_numpy_member,
diff --git a/astroid/brain/brain_numpy_core_multiarray.py b/astroid/brain/brain_numpy_core_multiarray.py
index 95ef0a3..97af794 100644
--- a/astroid/brain/brain_numpy_core_multiarray.py
+++ b/astroid/brain/brain_numpy_core_multiarray.py
@@ -17,20 +17,17 @@
from astroid.builder import parse
from astroid.inference_tip import inference_tip
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import Attribute, Name
def numpy_core_multiarray_transform() -> nodes.Module:
- return parse(
- """
+ return parse("""
# different functions defined in multiarray.py
def inner(a, b):
return numpy.ndarray([0, 0])
def vdot(a, b):
return numpy.ndarray([0, 0])
- """
- )
+ """)
METHODS_TO_BE_INFERRED = {
@@ -40,7 +37,7 @@
return numpy.ndarray([0, 0])""",
"empty_like": """def empty_like(a, dtype=None, order='K', subok=True):
return numpy.ndarray((0, 0))""",
- "concatenate": """def concatenate(arrays, axis=None, out=None):
+ "concatenate": """def concatenate(arrays, axis=None, out=None, dtype=None, casting='same_kind'):
return numpy.ndarray((0, 0))""",
"where": """def where(condition, x=None, y=None):
return numpy.ndarray([0, 0])""",
@@ -96,12 +93,12 @@
method_names = frozenset(METHODS_TO_BE_INFERRED.keys())
manager.register_transform(
- Attribute,
+ nodes.Attribute,
inference_tip(functools.partial(infer_numpy_attribute, METHODS_TO_BE_INFERRED)),
functools.partial(attribute_name_looks_like_numpy_member, method_names),
)
manager.register_transform(
- Name,
+ nodes.Name,
inference_tip(functools.partial(infer_numpy_name, METHODS_TO_BE_INFERRED)),
functools.partial(member_name_looks_like_numpy_member, method_names),
)
diff --git a/astroid/brain/brain_numpy_core_numeric.py b/astroid/brain/brain_numpy_core_numeric.py
index 72d4cc9..10c4603 100644
--- a/astroid/brain/brain_numpy_core_numeric.py
+++ b/astroid/brain/brain_numpy_core_numeric.py
@@ -15,25 +15,20 @@
from astroid.builder import parse
from astroid.inference_tip import inference_tip
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import Attribute
def numpy_core_numeric_transform() -> nodes.Module:
- return parse(
- """
+ return parse("""
# different functions defined in numeric.py
import numpy
def zeros_like(a, dtype=None, order='K', subok=True, shape=None): return numpy.ndarray((0, 0))
def ones_like(a, dtype=None, order='K', subok=True, shape=None): return numpy.ndarray((0, 0))
def full_like(a, fill_value, dtype=None, order='K', subok=True, shape=None): return numpy.ndarray((0, 0))
- """
- )
+ """)
-METHODS_TO_BE_INFERRED = {
- "ones": """def ones(shape, dtype=None, order='C'):
- return numpy.ndarray([0, 0])"""
-}
+METHODS_TO_BE_INFERRED = {"ones": """def ones(shape, dtype=None, order='C'):
+ return numpy.ndarray([0, 0])"""}
def register(manager: AstroidManager) -> None:
@@ -42,7 +37,7 @@
)
manager.register_transform(
- Attribute,
+ nodes.Attribute,
inference_tip(functools.partial(infer_numpy_attribute, METHODS_TO_BE_INFERRED)),
functools.partial(
attribute_name_looks_like_numpy_member,
diff --git a/astroid/brain/brain_numpy_core_numerictypes.py b/astroid/brain/brain_numpy_core_numerictypes.py
index 7111c83..2ebfa42 100644
--- a/astroid/brain/brain_numpy_core_numerictypes.py
+++ b/astroid/brain/brain_numpy_core_numerictypes.py
@@ -5,6 +5,7 @@
# TODO(hippo91) : correct the methods signature.
"""Astroid hooks for numpy.core.numerictypes module."""
+
from astroid import nodes
from astroid.brain.brain_numpy_utils import numpy_supports_type_hints
from astroid.brain.helpers import register_module_extender
@@ -109,9 +110,7 @@
def __class_getitem__(cls, value):
return cls
"""
- return parse(
- generic_src
- + """
+ return parse(generic_src + """
class dtype(object):
def __init__(self, obj, align=False, copy=False):
self.alignment = None
@@ -255,8 +254,7 @@
unicode_ = str_
ushort = uint16
void0 = void
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_numpy_core_umath.py b/astroid/brain/brain_numpy_core_umath.py
index a048a1c..d5701af 100644
--- a/astroid/brain/brain_numpy_core_umath.py
+++ b/astroid/brain/brain_numpy_core_umath.py
@@ -7,6 +7,7 @@
# typecheck in `_emit_no_member` function)
"""Astroid hooks for numpy.core.umath module."""
+
from astroid import nodes
from astroid.brain.helpers import register_module_extender
from astroid.builder import parse
@@ -18,8 +19,7 @@
"""out=None, where=True, casting='same_kind', order='K', """
"""dtype=None, subok=True"""
)
- return parse(
- """
+ return parse("""
class FakeUfunc:
def __init__(self):
self.__doc__ = str()
@@ -144,10 +144,7 @@
right_shift = FakeUfuncTwoArgs()
subtract = FakeUfuncTwoArgs()
true_divide = FakeUfuncTwoArgs()
- """.format(
- opt_args=ufunc_optional_keyword_arguments
- )
- )
+ """.format(opt_args=ufunc_optional_keyword_arguments))
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_numpy_ma.py b/astroid/brain/brain_numpy_ma.py
index e61acb5..24ab473 100644
--- a/astroid/brain/brain_numpy_ma.py
+++ b/astroid/brain/brain_numpy_ma.py
@@ -17,16 +17,14 @@
:param node: node to infer
:param context: inference context
"""
- return parse(
- """
+ return parse("""
import numpy.ma
def masked_where(condition, a, copy=True):
return numpy.ma.masked_array(a, mask=[])
def masked_invalid(a, copy=True):
return numpy.ma.masked_array(a, mask=[])
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_numpy_ndarray.py b/astroid/brain/brain_numpy_ndarray.py
index 38db4e6..ad79111 100644
--- a/astroid/brain/brain_numpy_ndarray.py
+++ b/astroid/brain/brain_numpy_ndarray.py
@@ -3,14 +3,15 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Astroid hooks for numpy ndarray class."""
+
from __future__ import annotations
+from astroid import nodes
from astroid.brain.brain_numpy_utils import numpy_supports_type_hints
from astroid.builder import extract_node
from astroid.context import InferenceContext
from astroid.inference_tip import inference_tip
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import Attribute
def infer_numpy_ndarray(node, context: InferenceContext | None = None):
@@ -151,13 +152,13 @@
return node.infer(context=context)
-def _looks_like_numpy_ndarray(node: Attribute) -> bool:
+def _looks_like_numpy_ndarray(node: nodes.Attribute) -> bool:
return node.attrname == "ndarray"
def register(manager: AstroidManager) -> None:
manager.register_transform(
- Attribute,
+ nodes.Attribute,
inference_tip(infer_numpy_ndarray),
_looks_like_numpy_ndarray,
)
diff --git a/astroid/brain/brain_numpy_random_mtrand.py b/astroid/brain/brain_numpy_random_mtrand.py
index be1c957..7976f2f 100644
--- a/astroid/brain/brain_numpy_random_mtrand.py
+++ b/astroid/brain/brain_numpy_random_mtrand.py
@@ -4,6 +4,7 @@
# TODO(hippo91) : correct the functions return types
"""Astroid hooks for numpy.random.mtrand module."""
+
from astroid import nodes
from astroid.brain.helpers import register_module_extender
from astroid.builder import parse
@@ -11,8 +12,7 @@
def numpy_random_mtrand_transform() -> nodes.Module:
- return parse(
- """
+ return parse("""
def beta(a, b, size=None): return uninferable
def binomial(n, p, size=None): return uninferable
def bytes(length): return uninferable
@@ -63,8 +63,7 @@
def wald(mean, scale, size=None): return uninferable
def weibull(a, size=None): return uninferable
def zipf(a, size=None): return uninferable
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_numpy_utils.py b/astroid/brain/brain_numpy_utils.py
index a3d4ed5..1a8f665 100644
--- a/astroid/brain/brain_numpy_utils.py
+++ b/astroid/brain/brain_numpy_utils.py
@@ -6,9 +6,9 @@
from __future__ import annotations
+from astroid import nodes
from astroid.builder import extract_node
from astroid.context import InferenceContext
-from astroid.nodes.node_classes import Attribute, Import, Name
# Class subscript is available in numpy starting with version 1.20.0
NUMPY_VERSION_TYPE_HINTS_SUPPORT = ("1", "20", "0")
@@ -35,20 +35,22 @@
def infer_numpy_name(
- sources: dict[str, str], node: Name, context: InferenceContext | None = None
+ sources: dict[str, str], node: nodes.Name, context: InferenceContext | None = None
):
extracted_node = extract_node(sources[node.name])
return extracted_node.infer(context=context)
def infer_numpy_attribute(
- sources: dict[str, str], node: Attribute, context: InferenceContext | None = None
+ sources: dict[str, str],
+ node: nodes.Attribute,
+ context: InferenceContext | None = None,
):
extracted_node = extract_node(sources[node.attrname])
return extracted_node.infer(context=context)
-def _is_a_numpy_module(node: Name) -> bool:
+def _is_a_numpy_module(node: nodes.Name) -> bool:
"""
Returns True if the node is a representation of a numpy module.
@@ -62,7 +64,7 @@
"""
module_nickname = node.name
potential_import_target = [
- x for x in node.lookup(module_nickname)[1] if isinstance(x, Import)
+ x for x in node.lookup(module_nickname)[1] if isinstance(x, nodes.Import)
]
return any(
("numpy", module_nickname) in target.names or ("numpy", None) in target.names
@@ -71,7 +73,7 @@
def member_name_looks_like_numpy_member(
- member_names: frozenset[str], node: Name
+ member_names: frozenset[str], node: nodes.Name
) -> bool:
"""
Returns True if the Name node's name matches a member name from numpy
@@ -80,13 +82,13 @@
def attribute_name_looks_like_numpy_member(
- member_names: frozenset[str], node: Attribute
+ member_names: frozenset[str], node: nodes.Attribute
) -> bool:
"""
Returns True if the Attribute node's name matches a member name from numpy
"""
return (
node.attrname in member_names
- and isinstance(node.expr, Name)
+ and isinstance(node.expr, nodes.Name)
and _is_a_numpy_module(node.expr)
)
diff --git a/astroid/brain/brain_pathlib.py b/astroid/brain/brain_pathlib.py
index 62daaaf..d1d1bda 100644
--- a/astroid/brain/brain_pathlib.py
+++ b/astroid/brain/brain_pathlib.py
@@ -8,7 +8,7 @@
from astroid import bases, context, nodes
from astroid.builder import _extract_single_node
-from astroid.const import PY313_PLUS
+from astroid.const import PY313
from astroid.exceptions import InferenceError, UseInferenceDefault
from astroid.inference_tip import inference_tip
from astroid.manager import AstroidManager
@@ -29,7 +29,7 @@
value = next(node.value.infer())
except (InferenceError, StopIteration):
return False
- parents = "builtins.tuple" if PY313_PLUS else "pathlib._PathParents"
+ parents = "builtins.tuple" if PY313 else "pathlib._PathParents"
return (
isinstance(value, bases.Instance)
and isinstance(value._proxied, nodes.ClassDef)
diff --git a/astroid/brain/brain_pkg_resources.py b/astroid/brain/brain_pkg_resources.py
index e2bd669..d3b8562 100644
--- a/astroid/brain/brain_pkg_resources.py
+++ b/astroid/brain/brain_pkg_resources.py
@@ -9,8 +9,7 @@
def pkg_resources_transform() -> nodes.Module:
- return parse(
- """
+ return parse("""
def require(*requirements):
return pkg_resources.working_set.require(*requirements)
@@ -64,8 +63,7 @@
return Distribution(dist)
_namespace_packages = {}
-"""
- )
+""")
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_pytest.py b/astroid/brain/brain_pytest.py
index 6d06267..827a24d 100644
--- a/astroid/brain/brain_pytest.py
+++ b/astroid/brain/brain_pytest.py
@@ -3,6 +3,7 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Astroid hooks for pytest."""
+
from astroid import nodes
from astroid.brain.helpers import register_module_extender
from astroid.builder import AstroidBuilder
@@ -10,8 +11,7 @@
def pytest_transform() -> nodes.Module:
- return AstroidBuilder(AstroidManager()).string_build(
- """
+ return AstroidBuilder(AstroidManager()).string_build("""
try:
import _pytest.mark
@@ -76,8 +76,7 @@
yield_fixture = _pytest.python.yield_fixture
except ImportError:
pass
-"""
- )
+""")
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_qt.py b/astroid/brain/brain_qt.py
index 30581e0..b384972 100644
--- a/astroid/brain/brain_qt.py
+++ b/astroid/brain/brain_qt.py
@@ -28,8 +28,7 @@
def transform_pyqt_signal(node: nodes.FunctionDef) -> None:
- module = parse(
- """
+ module = parse("""
_UNSET = object()
class pyqtSignal(object):
@@ -39,8 +38,7 @@
pass
def emit(self, *args):
pass
- """
- )
+ """)
signal_cls: nodes.ClassDef = module["pyqtSignal"]
node.instance_attrs["emit"] = [signal_cls["emit"]]
node.instance_attrs["disconnect"] = [signal_cls["disconnect"]]
@@ -48,8 +46,7 @@
def transform_pyside_signal(node: nodes.FunctionDef) -> None:
- module = parse(
- """
+ module = parse("""
class NotPySideSignal(object):
def connect(self, receiver, type=None):
pass
@@ -57,8 +54,7 @@
pass
def emit(self, *args):
pass
- """
- )
+ """)
signal_cls: nodes.ClassDef = module["NotPySideSignal"]
node.instance_attrs["connect"] = [signal_cls["connect"]]
node.instance_attrs["disconnect"] = [signal_cls["disconnect"]]
@@ -66,15 +62,13 @@
def pyqt4_qtcore_transform():
- return AstroidBuilder(AstroidManager()).string_build(
- """
+ return AstroidBuilder(AstroidManager()).string_build("""
def SIGNAL(signal_name): pass
class QObject(object):
def emit(self, signal): pass
-"""
- )
+""")
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_random.py b/astroid/brain/brain_random.py
index 48cc121..849a9f4 100644
--- a/astroid/brain/brain_random.py
+++ b/astroid/brain/brain_random.py
@@ -4,29 +4,21 @@
from __future__ import annotations
+import inspect
import random
+from astroid import nodes
from astroid.context import InferenceContext
from astroid.exceptions import UseInferenceDefault
from astroid.inference_tip import inference_tip
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import (
- Attribute,
- Call,
- Const,
- EvaluatedObject,
- List,
- Name,
- Set,
- Tuple,
-)
-from astroid.util import safe_infer
+from astroid.util import UninferableBase, safe_infer
-ACCEPTED_ITERABLES_FOR_SAMPLE = (List, Set, Tuple)
+ACCEPTED_ITERABLES_FOR_SAMPLE = (nodes.List, nodes.Set, nodes.Tuple)
def _clone_node_with_lineno(node, parent, lineno):
- if isinstance(node, EvaluatedObject):
+ if isinstance(node, nodes.EvaluatedObject):
node = node.original
cls = node.__class__
other_fields = node._other_fields
@@ -39,11 +31,19 @@
"end_col_offset": node.end_col_offset,
}
postinit_params = {param: getattr(node, param) for param in _astroid_fields}
- if other_fields:
- init_params.update({param: getattr(node, param) for param in other_fields})
+
+ valid_init_params = set(inspect.signature(cls.__init__).parameters)
+ for param in other_fields:
+ if param in valid_init_params:
+ init_params[param] = getattr(node, param)
+
new_node = cls(**init_params)
if hasattr(node, "postinit") and _astroid_fields:
new_node.postinit(**postinit_params)
+
+ for param in other_fields:
+ if param not in valid_init_params:
+ setattr(new_node, param, getattr(node, param))
return new_node
@@ -52,7 +52,7 @@
raise UseInferenceDefault
inferred_length = safe_infer(node.args[1], context=context)
- if not isinstance(inferred_length, Const):
+ if not isinstance(inferred_length, nodes.Const):
raise UseInferenceDefault
if not isinstance(inferred_length.value, int):
raise UseInferenceDefault
@@ -68,12 +68,15 @@
# In this case, this will raise a ValueError
raise UseInferenceDefault
+ if any(isinstance(elt, UninferableBase) for elt in inferred_sequence.elts):
+ raise UseInferenceDefault
+
try:
elts = random.sample(inferred_sequence.elts, inferred_length.value)
except ValueError as exc:
raise UseInferenceDefault from exc
- new_node = List(
+ new_node = nodes.List(
lineno=node.lineno,
col_offset=node.col_offset,
parent=node.scope(),
@@ -90,14 +93,14 @@
def _looks_like_random_sample(node) -> bool:
func = node.func
- if isinstance(func, Attribute):
+ if isinstance(func, nodes.Attribute):
return func.attrname == "sample"
- if isinstance(func, Name):
+ if isinstance(func, nodes.Name):
return func.name == "sample"
return False
def register(manager: AstroidManager) -> None:
manager.register_transform(
- Call, inference_tip(infer_random_sample), _looks_like_random_sample
+ nodes.Call, inference_tip(infer_random_sample), _looks_like_random_sample
)
diff --git a/astroid/brain/brain_re.py b/astroid/brain/brain_re.py
index 6464645..575434d 100644
--- a/astroid/brain/brain_re.py
+++ b/astroid/brain/brain_re.py
@@ -20,8 +20,7 @@
import_compiler = "import re._compiler as _compiler"
else:
import_compiler = "import sre_compile as _compiler"
- return parse(
- f"""
+ return parse(f"""
{import_compiler}
NOFLAG = 0
ASCII = _compiler.SRE_FLAG_ASCII
@@ -41,8 +40,7 @@
S = DOTALL
X = VERBOSE
T = TEMPLATE
- """
- )
+ """)
CLASS_GETITEM_TEMPLATE = """
diff --git a/astroid/brain/brain_regex.py b/astroid/brain/brain_regex.py
index 70fb946..9bc4b22 100644
--- a/astroid/brain/brain_regex.py
+++ b/astroid/brain/brain_regex.py
@@ -18,8 +18,7 @@
# pylint: disable-next=line-too-long
See https://github.com/mrabarnett/mrab-regex/blob/2022.10.31/regex_3/regex.py#L200
"""
- return parse(
- """
+ return parse("""
A = ASCII = 0x80 # Assume ASCII locale.
B = BESTMATCH = 0x1000 # Best fuzzy match.
D = DEBUG = 0x200 # Print parsed pattern.
@@ -39,8 +38,7 @@
W = WORD = 0x800 # Default Unicode word breaks.
X = VERBOSE = 0x40 # Ignore whitespace and comments.
T = TEMPLATE = 0x1 # Template (present because re module has it).
- """
- )
+ """)
CLASS_GETITEM_TEMPLATE = """
diff --git a/astroid/brain/brain_responses.py b/astroid/brain/brain_responses.py
index f2e6069..aed58ee 100644
--- a/astroid/brain/brain_responses.py
+++ b/astroid/brain/brain_responses.py
@@ -10,6 +10,7 @@
See: https://github.com/getsentry/responses/blob/master/responses.py
"""
+
from astroid import nodes
from astroid.brain.helpers import register_module_extender
from astroid.builder import parse
@@ -17,8 +18,7 @@
def responses_funcs() -> nodes.Module:
- return parse(
- """
+ return parse("""
DELETE = "DELETE"
GET = "GET"
HEAD = "HEAD"
@@ -72,8 +72,7 @@
def stop(allow_assert=True):
return
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_scipy_signal.py b/astroid/brain/brain_scipy_signal.py
index a7a2576..bbbd187 100755
--- a/astroid/brain/brain_scipy_signal.py
+++ b/astroid/brain/brain_scipy_signal.py
@@ -3,6 +3,7 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Astroid hooks for scipy.signal module."""
+
from astroid import nodes
from astroid.brain.helpers import register_module_extender
from astroid.builder import parse
@@ -10,8 +11,7 @@
def scipy_signal() -> nodes.Module:
- return parse(
- """
+ return parse("""
# different functions defined in scipy.signals
def barthann(M, sym=True):
@@ -82,8 +82,7 @@
def tukey(M, alpha=0.5, sym=True):
return numpy.ndarray([0])
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_signal.py b/astroid/brain/brain_signal.py
index 649e974..b725b1b 100644
--- a/astroid/brain/brain_signal.py
+++ b/astroid/brain/brain_signal.py
@@ -24,7 +24,6 @@
actual standard signal numbers - which may vary depending on the system.
"""
-
import sys
from astroid.brain.helpers import register_module_extender
diff --git a/astroid/brain/brain_six.py b/astroid/brain/brain_six.py
index c222a42..e589848 100644
--- a/astroid/brain/brain_six.py
+++ b/astroid/brain/brain_six.py
@@ -107,13 +107,11 @@
def six_moves_transform():
- code = dedent(
- """
+ code = dedent("""
class Moves(object):
{}
moves = Moves()
- """
- ).format(_indent(_IMPORTS, " "))
+ """).format(_indent(_IMPORTS, " "))
module = AstroidBuilder(AstroidManager()).string_build(code)
module.name = "six.moves"
return module
@@ -182,7 +180,11 @@
func = next(decorator.func.infer())
except (InferenceError, StopIteration):
continue
- if func.qname() == SIX_ADD_METACLASS and decorator.args:
+ if (
+ isinstance(func, (nodes.FunctionDef, nodes.ClassDef))
+ and func.qname() == SIX_ADD_METACLASS
+ and decorator.args
+ ):
metaclass = decorator.args[0]
node._metaclass = metaclass
return node
diff --git a/astroid/brain/brain_sqlalchemy.py b/astroid/brain/brain_sqlalchemy.py
index 8410d9e..77cac2d 100644
--- a/astroid/brain/brain_sqlalchemy.py
+++ b/astroid/brain/brain_sqlalchemy.py
@@ -9,8 +9,7 @@
def _session_transform() -> nodes.Module:
- return parse(
- """
+ return parse("""
from sqlalchemy.orm.session import Session
class sessionmaker:
@@ -33,8 +32,7 @@
return
return Session()
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_ssl.py b/astroid/brain/brain_ssl.py
index de932de..4cb89f8 100644
--- a/astroid/brain/brain_ssl.py
+++ b/astroid/brain/brain_ssl.py
@@ -7,7 +7,7 @@
from astroid import nodes
from astroid.brain.helpers import register_module_extender
from astroid.builder import parse
-from astroid.const import PY310_PLUS, PY312_PLUS
+from astroid.const import PY312_PLUS
from astroid.manager import AstroidManager
@@ -18,9 +18,7 @@
VERIFY_CRL_CHECK_LEAF = 1
VERIFY_CRL_CHECK_CHAIN = 2
VERIFY_X509_STRICT = 3
- VERIFY_X509_TRUSTED_FIRST = 4"""
- if PY310_PLUS:
- enum += """
+ VERIFY_X509_TRUSTED_FIRST = 4
VERIFY_ALLOW_PROXY_CERTS = 5
VERIFY_X509_PARTIAL_CHAIN = 6
"""
@@ -51,8 +49,7 @@
def ssl_transform() -> nodes.Module:
- return parse(
- f"""
+ return parse(f"""
# Import necessary for conversion of objects defined in C into enums
from enum import IntEnum as _IntEnum, IntFlag as _IntFlag
@@ -155,10 +152,7 @@
CERT_NONE = 0
CERT_OPTIONAL = 1
CERT_REQUIRED = 2
- """
- + _verifyflags_enum()
- + _options_enum()
- )
+ """ + _verifyflags_enum() + _options_enum())
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_statistics.py b/astroid/brain/brain_statistics.py
new file mode 100644
index 0000000..5420ef9
--- /dev/null
+++ b/astroid/brain/brain_statistics.py
@@ -0,0 +1,73 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
+
+"""Astroid hooks for understanding statistics library module.
+
+Provides inference improvements for statistics module functions that have
+complex runtime behavior difficult to analyze statically.
+"""
+
+from __future__ import annotations
+
+from collections.abc import Iterator
+from typing import TYPE_CHECKING
+
+from astroid import nodes
+from astroid.context import InferenceContext
+from astroid.inference_tip import inference_tip
+from astroid.manager import AstroidManager
+from astroid.util import Uninferable
+
+if TYPE_CHECKING:
+ from astroid.typing import InferenceResult
+
+
+def _looks_like_statistics_quantiles(node: nodes.Call) -> bool:
+ """Check if this is a call to statistics.quantiles."""
+ match node.func:
+ case nodes.Attribute(expr=nodes.Name(name="statistics"), attrname="quantiles"):
+ # Case 1: statistics.quantiles(...)
+ return True
+ case nodes.Name(name="quantiles"):
+ # Case 2: from statistics import quantiles; quantiles(...)
+ # Check if quantiles was imported from statistics
+ try:
+ frame = node.frame()
+ if "quantiles" in frame.locals:
+ # Look for import from statistics
+ for stmt in frame.body:
+ if (
+ isinstance(stmt, nodes.ImportFrom)
+ and stmt.modname == "statistics"
+ and any(name[0] == "quantiles" for name in stmt.names or [])
+ ):
+ return True
+ except (AttributeError, TypeError):
+ # If we can't determine the import context, be conservative
+ pass
+ return False
+
+
+def infer_statistics_quantiles(
+ node: nodes.Call, context: InferenceContext | None = None
+) -> Iterator[InferenceResult]:
+ """Infer the result of statistics.quantiles() calls.
+
+ Returns Uninferable because quantiles() has complex runtime behavior
+ that cannot be statically analyzed, preventing false positives in
+ pylint's unbalanced-tuple-unpacking checker.
+
+ statistics.quantiles() returns a list with (n-1) elements, but static
+ analysis sees only the empty list initializations in the function body.
+ """
+ yield Uninferable
+
+
+def register(manager: AstroidManager) -> None:
+ """Register statistics-specific inference improvements."""
+ manager.register_transform(
+ nodes.Call,
+ inference_tip(infer_statistics_quantiles),
+ _looks_like_statistics_quantiles,
+ )
diff --git a/astroid/brain/brain_subprocess.py b/astroid/brain/brain_subprocess.py
index 96855c6..e18a0fe 100644
--- a/astroid/brain/brain_subprocess.py
+++ b/astroid/brain/brain_subprocess.py
@@ -7,7 +7,7 @@
from astroid import nodes
from astroid.brain.helpers import register_module_extender
from astroid.builder import parse
-from astroid.const import PY310_PLUS, PY311_PLUS
+from astroid.const import PY311_PLUS
from astroid.manager import AstroidManager
@@ -19,10 +19,7 @@
preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None,
universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True,
start_new_session=False, pass_fds=(), *, encoding=None, errors=None, text=None,
- user=None, group=None, extra_groups=None, umask=-1"""
-
- if PY310_PLUS:
- args += ", pipesize=-1"
+ user=None, group=None, extra_groups=None, umask=-1, pipesize=-1"""
if PY311_PLUS:
args += ", process_group=None"
@@ -62,8 +59,7 @@
):
""".strip()
- code = textwrap.dedent(
- f"""
+ code = textwrap.dedent(f"""
def {check_output_signature}
if universal_newlines:
return ""
@@ -90,8 +86,7 @@
@classmethod
def __class_getitem__(cls, item):
pass
- """
- )
+ """)
init_lines = textwrap.dedent(init).splitlines()
indented_init = "\n".join(" " * 4 + line for line in init_lines)
diff --git a/astroid/brain/brain_threading.py b/astroid/brain/brain_threading.py
index 95af2db..a4ea976 100644
--- a/astroid/brain/brain_threading.py
+++ b/astroid/brain/brain_threading.py
@@ -9,8 +9,7 @@
def _thread_transform() -> nodes.Module:
- return parse(
- """
+ return parse("""
class lock(object):
def acquire(self, blocking=True, timeout=-1):
return False
@@ -25,8 +24,7 @@
def Lock(*args, **kwargs):
return lock()
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_type.py b/astroid/brain/brain_type.py
index 2fb06be..8391e59 100644
--- a/astroid/brain/brain_type.py
+++ b/astroid/brain/brain_type.py
@@ -35,7 +35,7 @@
Try to figure out if a Name node is used inside a type related subscript.
:param node: node to check
- :type node: astroid.nodes.node_classes.NodeNG
+ :type node: astroid.nodes.NodeNG
:return: whether the node is a Name node inside a type related subscript
"""
if isinstance(node.parent, nodes.Subscript):
@@ -48,12 +48,12 @@
Infer a type[...] subscript.
:param node: node to infer
- :type node: astroid.nodes.node_classes.NodeNG
+ :type node: astroid.nodes.NodeNG
:return: the inferred node
:rtype: nodes.NodeNG
"""
node_scope, _ = node.scope().lookup("type")
- if not isinstance(node_scope, nodes.Module) or node_scope.qname() != "builtins":
+ if not (isinstance(node_scope, nodes.Module) and node_scope.qname() == "builtins"):
raise UseInferenceDefault()
class_src = """
class type:
diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py
index 57cb56e..e0eb15f 100644
--- a/astroid/brain/brain_typing.py
+++ b/astroid/brain/brain_typing.py
@@ -12,10 +12,10 @@
from functools import partial
from typing import Final
-from astroid import context
+from astroid import context, nodes
from astroid.brain.helpers import register_module_extender
from astroid.builder import AstroidBuilder, _extract_single_node, extract_node
-from astroid.const import PY312_PLUS, PY313_PLUS
+from astroid.const import PY312_PLUS, PY313_PLUS, PY314_PLUS
from astroid.exceptions import (
AstroidSyntaxError,
AttributeInferenceError,
@@ -24,18 +24,6 @@
)
from astroid.inference_tip import inference_tip
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import (
- Assign,
- AssignName,
- Attribute,
- Call,
- Const,
- JoinedStr,
- Name,
- NodeNG,
- Subscript,
-)
-from astroid.nodes.scoped_nodes import ClassDef, FunctionDef
TYPING_TYPEVARS = {"TypeVar", "NewType"}
TYPING_TYPEVARS_QUALIFIED: Final = {
@@ -78,7 +66,7 @@
"typing.MutableMapping",
"typing.Sequence",
"typing.MutableSequence",
- "typing.ByteString",
+ "typing.ByteString", # scheduled for removal in 3.17
"typing.Tuple",
"typing.List",
"typing.Deque",
@@ -112,16 +100,16 @@
def looks_like_typing_typevar_or_newtype(node) -> bool:
func = node.func
- if isinstance(func, Attribute):
+ if isinstance(func, nodes.Attribute):
return func.attrname in TYPING_TYPEVARS
- if isinstance(func, Name):
+ if isinstance(func, nodes.Name):
return func.name in TYPING_TYPEVARS
return False
def infer_typing_typevar_or_newtype(
- node: Call, context_itton: context.InferenceContext | None = None
-) -> Iterator[ClassDef]:
+ node: nodes.Call, context_itton: context.InferenceContext | None = None
+) -> Iterator[nodes.ClassDef]:
"""Infer a typing.TypeVar(...) or typing.NewType(...) call."""
try:
func = next(node.func.infer(context=context_itton))
@@ -133,7 +121,7 @@
if not node.args:
raise UseInferenceDefault
# Cannot infer from a dynamic class name (f-string)
- if isinstance(node.args[0], JoinedStr):
+ if isinstance(node.args[0], nodes.JoinedStr):
raise UseInferenceDefault
typename = node.args[0].as_string().strip("'")
@@ -146,18 +134,18 @@
def _looks_like_typing_subscript(node) -> bool:
"""Try to figure out if a Subscript node *might* be a typing-related subscript."""
- if isinstance(node, Name):
+ if isinstance(node, nodes.Name):
return node.name in TYPING_MEMBERS
- if isinstance(node, Attribute):
+ if isinstance(node, nodes.Attribute):
return node.attrname in TYPING_MEMBERS
- if isinstance(node, Subscript):
+ if isinstance(node, nodes.Subscript):
return _looks_like_typing_subscript(node.value)
return False
def infer_typing_attr(
- node: Subscript, ctx: context.InferenceContext | None = None
-) -> Iterator[ClassDef]:
+ node: nodes.Subscript, ctx: context.InferenceContext | None = None
+) -> Iterator[nodes.ClassDef]:
"""Infer a typing.X[...] subscript."""
try:
value = next(node.value.infer()) # type: ignore[union-attr] # value shouldn't be None for Subscript.
@@ -170,14 +158,14 @@
if (
PY313_PLUS
- and isinstance(value, FunctionDef)
+ and isinstance(value, nodes.FunctionDef)
and value.qname() == "typing.Annotated"
):
# typing.Annotated is a FunctionDef on 3.13+
node._explicit_inference = lambda node, context: iter([value])
return iter([value])
- if isinstance(value, ClassDef) and value.qname() in {
+ if isinstance(value, nodes.ClassDef) and value.qname() in {
"typing.Generic",
"typing.Annotated",
"typing_extensions.Annotated",
@@ -188,7 +176,7 @@
func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE)
value.locals["__class_getitem__"] = [func_to_add]
if (
- isinstance(node.parent, ClassDef)
+ isinstance(node.parent, nodes.ClassDef)
and node in node.parent.bases
and getattr(node.parent, "__cache", None)
):
@@ -205,14 +193,14 @@
return node.infer(context=ctx)
-def _looks_like_generic_class_pep695(node: ClassDef) -> bool:
+def _looks_like_generic_class_pep695(node: nodes.ClassDef) -> bool:
"""Check if class is using type parameter. Python 3.12+."""
return len(node.type_params) > 0
def infer_typing_generic_class_pep695(
- node: ClassDef, ctx: context.InferenceContext | None = None
-) -> Iterator[ClassDef]:
+ node: nodes.ClassDef, ctx: context.InferenceContext | None = None
+) -> Iterator[nodes.ClassDef]:
"""Add __class_getitem__ for generic classes. Python 3.12+."""
func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE)
node.locals["__class_getitem__"] = [func_to_add]
@@ -220,17 +208,17 @@
def _looks_like_typedDict( # pylint: disable=invalid-name
- node: FunctionDef | ClassDef,
+ node: nodes.FunctionDef | nodes.ClassDef,
) -> bool:
"""Check if node is TypedDict FunctionDef."""
return node.qname() in TYPING_TYPEDDICT_QUALIFIED
def infer_typedDict( # pylint: disable=invalid-name
- node: FunctionDef, ctx: context.InferenceContext | None = None
-) -> Iterator[ClassDef]:
+ node: nodes.FunctionDef, ctx: context.InferenceContext | None = None
+) -> Iterator[nodes.ClassDef]:
"""Replace TypedDict FunctionDef with ClassDef."""
- class_def = ClassDef(
+ class_def = nodes.ClassDef(
name="TypedDict",
lineno=node.lineno,
col_offset=node.col_offset,
@@ -244,7 +232,7 @@
return iter([class_def])
-def _looks_like_typing_alias(node: Call) -> bool:
+def _looks_like_typing_alias(node: nodes.Call) -> bool:
"""
Returns True if the node corresponds to a call to _alias function.
@@ -255,18 +243,18 @@
:param node: call node
"""
return (
- isinstance(node.func, Name)
+ isinstance(node.func, nodes.Name)
# TODO: remove _DeprecatedGenericAlias when Py3.14 min
and node.func.name in {"_alias", "_DeprecatedGenericAlias"}
and len(node.args) == 2
and (
# _alias function works also for builtins object such as list and dict
- isinstance(node.args[0], (Attribute, Name))
+ isinstance(node.args[0], (nodes.Attribute, nodes.Name))
)
)
-def _forbid_class_getitem_access(node: ClassDef) -> None:
+def _forbid_class_getitem_access(node: nodes.ClassDef) -> None:
"""Disable the access to __class_getitem__ method for the node in parameters."""
def full_raiser(origin_func, attr, *args, **kwargs):
@@ -291,8 +279,8 @@
def infer_typing_alias(
- node: Call, ctx: context.InferenceContext | None = None
-) -> Iterator[ClassDef]:
+ node: nodes.Call, ctx: context.InferenceContext | None = None
+) -> Iterator[nodes.ClassDef]:
"""
Infers the call to _alias function
Insert ClassDef, with same name as aliased class,
@@ -303,10 +291,10 @@
# TODO: evaluate if still necessary when Py3.12 is minimum
"""
- if (
- not isinstance(node.parent, Assign)
- or not len(node.parent.targets) == 1
- or not isinstance(node.parent.targets[0], AssignName)
+ if not (
+ isinstance(node.parent, nodes.Assign)
+ and len(node.parent.targets) == 1
+ and isinstance(node.parent.targets[0], nodes.AssignName)
):
raise UseInferenceDefault
try:
@@ -316,7 +304,7 @@
assign_name = node.parent.targets[0]
- class_def = ClassDef(
+ class_def = nodes.ClassDef(
name=assign_name.name,
lineno=assign_name.lineno,
col_offset=assign_name.col_offset,
@@ -324,13 +312,13 @@
end_lineno=assign_name.end_lineno,
end_col_offset=assign_name.end_col_offset,
)
- if isinstance(res, ClassDef):
+ if isinstance(res, nodes.ClassDef):
# Only add `res` as base if it's a `ClassDef`
# This isn't the case for `typing.Pattern` and `typing.Match`
class_def.postinit(bases=[res], body=[], decorators=None)
maybe_type_var = node.args[1]
- if isinstance(maybe_type_var, Const) and maybe_type_var.value > 0:
+ if isinstance(maybe_type_var, nodes.Const) and maybe_type_var.value > 0:
# If typing alias is subscriptable, add `__class_getitem__` to ClassDef
func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE)
class_def.locals["__class_getitem__"] = [func_to_add]
@@ -345,7 +333,7 @@
return iter([class_def])
-def _looks_like_special_alias(node: Call) -> bool:
+def _looks_like_special_alias(node: nodes.Call) -> bool:
"""Return True if call is for Tuple or Callable alias.
In PY37 and PY38 the call is to '_VariadicGenericAlias' with 'tuple' as
@@ -357,28 +345,32 @@
PY37: Callable = _VariadicGenericAlias(collections.abc.Callable, (), special=True)
PY39: Callable = _CallableType(collections.abc.Callable, 2)
"""
- return isinstance(node.func, Name) and (
- (
- node.func.name == "_TupleType"
- and isinstance(node.args[0], Name)
- and node.args[0].name == "tuple"
- )
- or (
- node.func.name == "_CallableType"
- and isinstance(node.args[0], Attribute)
- and node.args[0].as_string() == "collections.abc.Callable"
+ return (
+ isinstance(node.func, nodes.Name)
+ and node.args
+ and (
+ (
+ node.func.name == "_TupleType"
+ and isinstance(node.args[0], nodes.Name)
+ and node.args[0].name == "tuple"
+ )
+ or (
+ node.func.name == "_CallableType"
+ and isinstance(node.args[0], nodes.Attribute)
+ and node.args[0].as_string() == "collections.abc.Callable"
+ )
)
)
def infer_special_alias(
- node: Call, ctx: context.InferenceContext | None = None
-) -> Iterator[ClassDef]:
+ node: nodes.Call, ctx: context.InferenceContext | None = None
+) -> Iterator[nodes.ClassDef]:
"""Infer call to tuple alias as new subscriptable class typing.Tuple."""
if not (
- isinstance(node.parent, Assign)
+ isinstance(node.parent, nodes.Assign)
and len(node.parent.targets) == 1
- and isinstance(node.parent.targets[0], AssignName)
+ and isinstance(node.parent.targets[0], nodes.AssignName)
):
raise UseInferenceDefault
try:
@@ -387,7 +379,7 @@
raise InferenceError(node=node.args[0], context=ctx) from e
assign_name = node.parent.targets[0]
- class_def = ClassDef(
+ class_def = nodes.ClassDef(
name=assign_name.name,
parent=node.parent,
lineno=assign_name.lineno,
@@ -403,27 +395,27 @@
return iter([class_def])
-def _looks_like_typing_cast(node: Call) -> bool:
- return (isinstance(node.func, Name) and node.func.name == "cast") or (
- isinstance(node.func, Attribute) and node.func.attrname == "cast"
+def _looks_like_typing_cast(node: nodes.Call) -> bool:
+ return (isinstance(node.func, nodes.Name) and node.func.name == "cast") or (
+ isinstance(node.func, nodes.Attribute) and node.func.attrname == "cast"
)
def infer_typing_cast(
- node: Call, ctx: context.InferenceContext | None = None
-) -> Iterator[NodeNG]:
+ node: nodes.Call, ctx: context.InferenceContext | None = None
+) -> Iterator[nodes.NodeNG]:
"""Infer call to cast() returning same type as casted-from var."""
- if not isinstance(node.func, (Name, Attribute)):
+ if not isinstance(node.func, (nodes.Name, nodes.Attribute)):
raise UseInferenceDefault
try:
func = next(node.func.infer(context=ctx))
except (InferenceError, StopIteration) as exc:
raise UseInferenceDefault from exc
- if (
- not isinstance(func, FunctionDef)
- or func.qname() != "typing.cast"
- or len(node.args) != 2
+ if not (
+ isinstance(func, nodes.FunctionDef)
+ and func.qname() == "typing.cast"
+ and len(node.args) == 2
):
raise UseInferenceDefault
@@ -431,9 +423,7 @@
def _typing_transform():
- return AstroidBuilder(AstroidManager()).string_build(
- textwrap.dedent(
- """
+ code = textwrap.dedent("""
class Generic:
@classmethod
def __class_getitem__(cls, item): return cls
@@ -466,39 +456,45 @@
class Match:
@classmethod
def __class_getitem__(cls, item): return cls
- """
- )
- )
+ """)
+ if PY314_PLUS:
+ code += textwrap.dedent("""
+ from annotationlib import ForwardRef
+ class Union:
+ @classmethod
+ def __class_getitem__(cls, item): return cls
+ """)
+ return AstroidBuilder(AstroidManager()).string_build(code)
def register(manager: AstroidManager) -> None:
manager.register_transform(
- Call,
+ nodes.Call,
inference_tip(infer_typing_typevar_or_newtype),
looks_like_typing_typevar_or_newtype,
)
manager.register_transform(
- Subscript, inference_tip(infer_typing_attr), _looks_like_typing_subscript
+ nodes.Subscript, inference_tip(infer_typing_attr), _looks_like_typing_subscript
)
manager.register_transform(
- Call, inference_tip(infer_typing_cast), _looks_like_typing_cast
+ nodes.Call, inference_tip(infer_typing_cast), _looks_like_typing_cast
)
manager.register_transform(
- FunctionDef, inference_tip(infer_typedDict), _looks_like_typedDict
+ nodes.FunctionDef, inference_tip(infer_typedDict), _looks_like_typedDict
)
manager.register_transform(
- Call, inference_tip(infer_typing_alias), _looks_like_typing_alias
+ nodes.Call, inference_tip(infer_typing_alias), _looks_like_typing_alias
)
manager.register_transform(
- Call, inference_tip(infer_special_alias), _looks_like_special_alias
+ nodes.Call, inference_tip(infer_special_alias), _looks_like_special_alias
)
if PY312_PLUS:
register_module_extender(manager, "typing", _typing_transform)
manager.register_transform(
- ClassDef,
+ nodes.ClassDef,
inference_tip(infer_typing_generic_class_pep695),
_looks_like_generic_class_pep695,
)
diff --git a/astroid/brain/brain_unittest.py b/astroid/brain/brain_unittest.py
index 4103ce0..64bd367 100644
--- a/astroid/brain/brain_unittest.py
+++ b/astroid/brain/brain_unittest.py
@@ -3,6 +3,7 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Astroid hooks for unittest module."""
+
from astroid import nodes
from astroid.brain.helpers import register_module_extender
from astroid.builder import parse
@@ -20,11 +21,9 @@
(see https://github.com/pylint-dev/pylint/issues/4060)
"""
- return parse(
- """
+ return parse("""
from .async_case import IsolatedAsyncioTestCase
- """
- )
+ """)
def register(manager: AstroidManager) -> None:
diff --git a/astroid/brain/brain_uuid.py b/astroid/brain/brain_uuid.py
index 37800b8..b10bdea 100644
--- a/astroid/brain/brain_uuid.py
+++ b/astroid/brain/brain_uuid.py
@@ -3,17 +3,17 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Astroid hooks for the UUID module."""
+
+from astroid import nodes
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import Const
-from astroid.nodes.scoped_nodes import ClassDef
-def _patch_uuid_class(node: ClassDef) -> None:
+def _patch_uuid_class(node: nodes.ClassDef) -> None:
# The .int member is patched using __dict__
- node.locals["int"] = [Const(0, parent=node)]
+ node.locals["int"] = [nodes.Const(0, parent=node)]
def register(manager: AstroidManager) -> None:
manager.register_transform(
- ClassDef, _patch_uuid_class, lambda node: node.qname() == "uuid.UUID"
+ nodes.ClassDef, _patch_uuid_class, lambda node: node.qname() == "uuid.UUID"
)
diff --git a/astroid/brain/helpers.py b/astroid/brain/helpers.py
index 79d778b..0064a1f 100644
--- a/astroid/brain/helpers.py
+++ b/astroid/brain/helpers.py
@@ -5,10 +5,15 @@
from __future__ import annotations
from collections.abc import Callable
+from typing import TYPE_CHECKING
+from astroid.exceptions import InferenceError
from astroid.manager import AstroidManager
from astroid.nodes.scoped_nodes import Module
+if TYPE_CHECKING:
+ from astroid.nodes.node_ng import NodeNG
+
def register_module_extender(
manager: AstroidManager, module_name: str, get_extension_mod: Callable[[], Module]
@@ -47,7 +52,6 @@
brain_mechanize,
brain_multiprocessing,
brain_namedtuple_enum,
- brain_nose,
brain_numpy_core_einsumfunc,
brain_numpy_core_fromnumeric,
brain_numpy_core_function_base,
@@ -71,6 +75,7 @@
brain_six,
brain_sqlalchemy,
brain_ssl,
+ brain_statistics,
brain_subprocess,
brain_threading,
brain_type,
@@ -99,7 +104,6 @@
brain_mechanize.register(manager)
brain_multiprocessing.register(manager)
brain_namedtuple_enum.register(manager)
- brain_nose.register(manager)
brain_numpy_core_einsumfunc.register(manager)
brain_numpy_core_fromnumeric.register(manager)
brain_numpy_core_function_base.register(manager)
@@ -123,9 +127,20 @@
brain_six.register(manager)
brain_sqlalchemy.register(manager)
brain_ssl.register(manager)
+ brain_statistics.register(manager)
brain_subprocess.register(manager)
brain_threading.register(manager)
brain_type.register(manager)
brain_typing.register(manager)
brain_unittest.register(manager)
brain_uuid.register(manager)
+
+
+def is_class_var(node: NodeNG) -> bool:
+ """Return True if node is a ClassVar, with or without subscripting."""
+ try:
+ inferred = next(node.infer())
+ except (InferenceError, StopIteration):
+ return False
+
+ return getattr(inferred, "name", "") == "ClassVar"
diff --git a/astroid/builder.py b/astroid/builder.py
index 6fa7be6..f166ab4 100644
--- a/astroid/builder.py
+++ b/astroid/builder.py
@@ -16,14 +16,14 @@
import textwrap
import types
import warnings
-from collections.abc import Iterator, Sequence
+from collections.abc import Collection, Iterator, Sequence
from io import TextIOWrapper
from tokenize import detect_encoding
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, cast
from astroid import bases, modutils, nodes, raw_building, rebuilder, util
from astroid._ast import ParserModule, get_parser_module
-from astroid.const import PY312_PLUS
+from astroid.const import PY312_PLUS, PY314_PLUS
from astroid.exceptions import AstroidBuildingError, AstroidSyntaxError, InferenceError
if TYPE_CHECKING:
@@ -39,7 +39,11 @@
_STATEMENT_SELECTOR = "#@"
if PY312_PLUS:
- warnings.filterwarnings("ignore", "invalid escape sequence", SyntaxWarning)
+ warnings.filterwarnings("ignore", ".*invalid escape sequence", SyntaxWarning)
+if PY314_PLUS:
+ warnings.filterwarnings(
+ "ignore", "'(return|continue|break)' in a 'finally'", SyntaxWarning
+ )
def open_source_file(filename: str) -> tuple[TextIOWrapper, str, str]:
@@ -159,11 +163,11 @@
module.file_encoding = encoding
self._manager.cache_module(module)
# post tree building steps after we stored the module in the cache:
- for from_node in builder._import_from_nodes:
+ for from_node, global_names in builder._import_from_nodes:
if from_node.modname == "__future__":
for symbol, _ in from_node.names:
module.future_imports.add(symbol)
- self.add_from_names_to_locals(from_node)
+ self.add_from_names_to_locals(from_node, global_names)
# handle delayed assattr nodes
for delayed in builder._delayed_assattr:
self.delayed_assattr(delayed)
@@ -181,7 +185,7 @@
node, parser_module = _parse_string(
data, type_comments=True, modname=modname
)
- except (TypeError, ValueError, SyntaxError) as exc:
+ except (TypeError, ValueError, SyntaxError, MemoryError) as exc:
raise AstroidSyntaxError(
"Parsing Python code failed:\n{error}",
source=data,
@@ -206,19 +210,23 @@
module = builder.visit_module(node, modname, node_file, package)
return module, builder
- def add_from_names_to_locals(self, node: nodes.ImportFrom) -> None:
+ def add_from_names_to_locals(
+ self, node: nodes.ImportFrom, global_name: Collection[str]
+ ) -> None:
"""Store imported names to the locals.
Resort the locals if coming from a delayed node
"""
- def _key_func(node: nodes.NodeNG) -> int:
- return node.fromlineno or 0
-
- def sort_locals(my_list: list[nodes.NodeNG]) -> None:
- my_list.sort(key=_key_func)
+ def add_local(parent_or_root: nodes.NodeNG, name: str) -> None:
+ parent_or_root.set_local(name, node)
+ my_list = parent_or_root.scope().locals[name]
+ if TYPE_CHECKING:
+ my_list = cast(list[nodes.NodeNG], my_list)
+ my_list.sort(key=lambda n: n.fromlineno or 0)
assert node.parent # It should always default to the module
+ module = node.root()
for name, asname in node.names:
if name == "*":
try:
@@ -226,11 +234,16 @@
except AstroidBuildingError:
continue
for name in imported.public_names():
- node.parent.set_local(name, node)
- sort_locals(node.parent.scope().locals[name]) # type: ignore[arg-type]
+ if name in global_name:
+ add_local(module, name)
+ else:
+ add_local(node.parent, name)
else:
- node.parent.set_local(asname or name, node)
- sort_locals(node.parent.scope().locals[asname or name]) # type: ignore[arg-type]
+ name = asname or name
+ if name in global_name:
+ add_local(module, name)
+ else:
+ add_local(node.parent, name)
def delayed_assattr(self, node: nodes.AssignAttr) -> None:
"""Visit an AssignAttr node.
@@ -317,6 +330,7 @@
isinstance(node, nodes.Call)
and isinstance(node.func, nodes.Name)
and node.func.name == _TRANSIENT_FUNCTION
+ and node.args
):
real_expr = node.args[0]
assert node.parent
diff --git a/astroid/const.py b/astroid/const.py
index c010818..dcce074 100644
--- a/astroid/const.py
+++ b/astroid/const.py
@@ -5,10 +5,11 @@
import enum
import sys
-PY310_PLUS = sys.version_info >= (3, 10)
PY311_PLUS = sys.version_info >= (3, 11)
PY312_PLUS = sys.version_info >= (3, 12)
+PY313 = sys.version_info[:2] == (3, 13)
PY313_PLUS = sys.version_info >= (3, 13)
+PY314_PLUS = sys.version_info >= (3, 14)
WIN32 = sys.platform == "win32"
diff --git a/astroid/constraint.py b/astroid/constraint.py
index 08bb80e..0dfecc3 100644
--- a/astroid/constraint.py
+++ b/astroid/constraint.py
@@ -3,14 +3,16 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Classes representing different types of constraints on inference values."""
+
from __future__ import annotations
import sys
from abc import ABC, abstractmethod
from collections.abc import Iterator
-from typing import TYPE_CHECKING, Union
+from typing import TYPE_CHECKING
-from astroid import nodes, util
+from astroid import helpers, nodes, util
+from astroid.exceptions import AstroidTypeError, InferenceError, MroError
from astroid.typing import InferenceResult
if sys.version_info >= (3, 11):
@@ -21,7 +23,7 @@
if TYPE_CHECKING:
from astroid import bases
-_NameNodes = Union[nodes.AssignAttr, nodes.Attribute, nodes.AssignName, nodes.Name]
+_NameNodes = nodes.AssignAttr | nodes.Attribute | nodes.AssignName | nodes.Name
class Constraint(ABC):
@@ -77,16 +79,160 @@
def satisfied_by(self, inferred: InferenceResult) -> bool:
"""Return True if this constraint is satisfied by the given inferred value."""
# Assume true if uninferable
- if isinstance(inferred, util.UninferableBase):
+ if inferred is util.Uninferable:
return True
# Return the XOR of self.negate and matches(inferred, self.CONST_NONE)
return self.negate ^ _matches(inferred, self.CONST_NONE)
+class BooleanConstraint(Constraint):
+ """Represents an "x" or "not x" constraint."""
+
+ @classmethod
+ def match(
+ cls, node: _NameNodes, expr: nodes.NodeNG, negate: bool = False
+ ) -> Self | None:
+ """Return a new constraint for node if expr matches one of these patterns:
+
+ - direct match (expr == node): use given negate value
+ - negated match (expr == `not node`): flip negate value
+
+ Return None if no pattern matches.
+ """
+ if _matches(expr, node):
+ return cls(node=node, negate=negate)
+
+ if (
+ isinstance(expr, nodes.UnaryOp)
+ and expr.op == "not"
+ and _matches(expr.operand, node)
+ ):
+ return cls(node=node, negate=not negate)
+
+ return None
+
+ def satisfied_by(self, inferred: InferenceResult) -> bool:
+ """Return True for uninferable results, or depending on negate flag:
+
+ - negate=False: satisfied if boolean value is True
+ - negate=True: satisfied if boolean value is False
+ """
+ inferred_booleaness = inferred.bool_value()
+ if inferred is util.Uninferable or inferred_booleaness is util.Uninferable:
+ return True
+
+ return self.negate ^ inferred_booleaness
+
+
+class TypeConstraint(Constraint):
+ """Represents an "isinstance(x, y)" constraint."""
+
+ def __init__(
+ self, node: nodes.NodeNG, classinfo: nodes.NodeNG, negate: bool
+ ) -> None:
+ super().__init__(node=node, negate=negate)
+ self.classinfo = classinfo
+
+ @classmethod
+ def match(
+ cls, node: _NameNodes, expr: nodes.NodeNG, negate: bool = False
+ ) -> Self | None:
+ """Return a new constraint for node if expr matches the
+ "isinstance(x, y)" pattern. Else, return None.
+ """
+ is_instance_call = (
+ isinstance(expr, nodes.Call)
+ and isinstance(expr.func, nodes.Name)
+ and expr.func.name == "isinstance"
+ and not expr.keywords
+ and len(expr.args) == 2
+ )
+ if is_instance_call and _matches(expr.args[0], node):
+ return cls(node=node, classinfo=expr.args[1], negate=negate)
+
+ return None
+
+ def satisfied_by(self, inferred: InferenceResult) -> bool:
+ """Return True for uninferable results, or depending on negate flag:
+
+ - negate=False: satisfied when inferred is an instance of the checked types.
+ - negate=True: satisfied when inferred is not an instance of the checked types.
+ """
+ if inferred is util.Uninferable:
+ return True
+
+ try:
+ types = helpers.class_or_tuple_to_container(self.classinfo)
+ matches_checked_types = helpers.object_isinstance(inferred, types)
+
+ if matches_checked_types is util.Uninferable:
+ return True
+
+ return self.negate ^ matches_checked_types
+ except (InferenceError, AstroidTypeError, MroError):
+ return True
+
+
+class EqualityConstraint(Constraint):
+ """Represents a "==" or "!=" constraint."""
+
+ def __init__(self, node: nodes.NodeNG, operand: nodes.NodeNG, negate: bool) -> None:
+ super().__init__(node=node, negate=negate)
+ self.operand = operand
+
+ @classmethod
+ def match(
+ cls, node: _NameNodes, expr: nodes.NodeNG, negate: bool = False
+ ) -> Self | None:
+ """Return a new constraint for node if expr matches one of these patterns:
+
+ - "node == operand" or "operand == node": use given negate value
+ - "node != operand" or "operand != node": flip negate value
+
+ Return None if no pattern matches.
+ """
+ if isinstance(expr, nodes.Compare) and len(expr.ops) == 1:
+ left = expr.left
+ op, right = expr.ops[0]
+ matches_left = _matches(left, node)
+
+ if op in {"==", "!="} and (matches_left or _matches(right, node)):
+ operand = right if matches_left else left
+ negate = (op == "==" and negate) or (op == "!=" and not negate)
+ return cls(node=node, operand=operand, negate=negate)
+
+ return None
+
+ def satisfied_by(self, inferred: InferenceResult) -> bool:
+ """Return True for uninferable/ambiguous results, or depending on negate flag:
+
+ - negate=False: satisfied when both operands are equal.
+ - negate=True: satisfied when both operands are not equal.
+
+ Only comparisons between constants and callables are supported.
+ """
+ if inferred is util.Uninferable:
+ return True
+
+ operand_inferred = util.safe_infer(self.operand)
+ if operand_inferred is util.Uninferable or operand_inferred is None:
+ return True
+
+ if isinstance(inferred, nodes.Const) and isinstance(
+ operand_inferred, nodes.Const
+ ):
+ return self.negate ^ (inferred.value == operand_inferred.value)
+
+ if inferred.callable() and operand_inferred.callable():
+ return self.negate ^ (inferred is operand_inferred)
+
+ return True
+
+
def get_constraints(
expr: _NameNodes, frame: nodes.LocalsDictNodeNG
-) -> dict[nodes.If, set[Constraint]]:
+) -> dict[nodes.If | nodes.IfExp, set[Constraint]]:
"""Returns the constraints for the given expression.
The returned dictionary maps the node where the constraint was generated to the
@@ -96,10 +242,10 @@
Currently this only supports constraints generated from if conditions.
"""
current_node: nodes.NodeNG | None = expr
- constraints_mapping: dict[nodes.If, set[Constraint]] = {}
+ constraints_mapping: dict[nodes.If | nodes.IfExp, set[Constraint]] = {}
while current_node is not None and current_node is not frame:
parent = current_node.parent
- if isinstance(parent, nodes.If):
+ if isinstance(parent, (nodes.If, nodes.IfExp)):
branch, _ = parent.locate_child(current_node)
constraints: set[Constraint] | None = None
if branch == "body":
@@ -114,7 +260,14 @@
return constraints_mapping
-ALL_CONSTRAINT_CLASSES = frozenset((NoneConstraint,))
+ALL_CONSTRAINT_CLASSES = frozenset(
+ (
+ NoneConstraint,
+ BooleanConstraint,
+ TypeConstraint,
+ EqualityConstraint,
+ )
+)
"""All supported constraint types."""
diff --git a/astroid/context.py b/astroid/context.py
index 777565a..fa9ed22 100644
--- a/astroid/context.py
+++ b/astroid/context.py
@@ -9,17 +9,15 @@
import contextlib
import pprint
from collections.abc import Iterator, Sequence
-from typing import TYPE_CHECKING, Optional
+from typing import TYPE_CHECKING
from astroid.typing import InferenceResult, SuccessfulInferenceResult
if TYPE_CHECKING:
from astroid import constraint, nodes
- from astroid.nodes.node_classes import Keyword
- from astroid.nodes.node_ng import NodeNG
_InferenceCache = dict[
- tuple["NodeNG", Optional[str], Optional[str], Optional[str]], Sequence["NodeNG"]
+ tuple["nodes.NodeNG", str | None, str | None, str | None], Sequence["nodes.NodeNG"]
]
_INFERENCE_CACHE: _InferenceCache = {}
@@ -80,7 +78,9 @@
self.extra_context: dict[SuccessfulInferenceResult, InferenceContext] = {}
"""Context that needs to be passed down through call stacks for call arguments."""
- self.constraints: dict[str, dict[nodes.If, set[constraint.Constraint]]] = {}
+ self.constraints: dict[
+ str, dict[nodes.If | nodes.IfExp, set[constraint.Constraint]]
+ ] = {}
"""The constraints on nodes."""
@property
@@ -168,8 +168,8 @@
def __init__(
self,
- args: list[NodeNG],
- keywords: list[Keyword] | None = None,
+ args: list[nodes.NodeNG],
+ keywords: list[nodes.Keyword] | None = None,
callee: InferenceResult | None = None,
):
self.args = args # Call positional arguments
diff --git a/astroid/decorators.py b/astroid/decorators.py
index 93c5fc9..05d2dd3 100644
--- a/astroid/decorators.py
+++ b/astroid/decorators.py
@@ -11,18 +11,13 @@
import sys
import warnings
from collections.abc import Callable, Generator
-from typing import TypeVar
+from typing import ParamSpec, TypeVar
from astroid import util
from astroid.context import InferenceContext
from astroid.exceptions import InferenceError
from astroid.typing import InferenceResult
-if sys.version_info >= (3, 10):
- from typing import ParamSpec
-else:
- from typing_extensions import ParamSpec
-
_R = TypeVar("_R")
_P = ParamSpec("_P")
diff --git a/astroid/exceptions.py b/astroid/exceptions.py
index 126acb9..e523b70 100644
--- a/astroid/exceptions.py
+++ b/astroid/exceptions.py
@@ -64,7 +64,10 @@
setattr(self, key, value)
def __str__(self) -> str:
- return self.message.format(**vars(self))
+ try:
+ return self.message.format(**vars(self))
+ except ValueError:
+ return self.message # Return raw message if formatting fails
class AstroidBuildingError(AstroidError):
diff --git a/astroid/helpers.py b/astroid/helpers.py
index 62a7259..deef3d9 100644
--- a/astroid/helpers.py
+++ b/astroid/helpers.py
@@ -104,7 +104,7 @@
types = set(_object_type(node, context))
except InferenceError:
return util.Uninferable
- if len(types) > 1 or not types:
+ if len(types) != 1:
return util.Uninferable
return next(iter(types))
@@ -129,7 +129,9 @@
# issubclass(object, (1, type)) raises TypeError
for klass in class_seq:
if isinstance(klass, util.UninferableBase):
- raise AstroidTypeError("arg 2 must be a type or tuple of types")
+ raise AstroidTypeError(
+ f"arg 2 must be a type or tuple of types, not {type(klass)!r}"
+ )
for obj_subclass in obj_type.mro():
if obj_subclass == klass:
@@ -164,10 +166,34 @@
or its type's mro doesn't work
"""
if not isinstance(node, nodes.ClassDef):
- raise TypeError(f"{node} needs to be a ClassDef node")
+ raise TypeError(f"{node} needs to be a ClassDef node, not {type(node)!r}")
return _object_type_is_subclass(node, class_or_seq, context=context)
+def class_or_tuple_to_container(
+ node: InferenceResult, context: InferenceContext | None = None
+) -> list[InferenceResult]:
+ # Move inferences results into container
+ # to simplify later logic
+ # raises InferenceError if any of the inferences fall through
+ try:
+ node_infer = next(node.infer(context=context))
+ except StopIteration as e: # pragma: no cover
+ raise InferenceError(node=node, context=context) from e
+ # arg2 MUST be a type or a TUPLE of types
+ # for isinstance
+ if isinstance(node_infer, nodes.Tuple):
+ try:
+ class_container = [
+ next(node.infer(context=context)) for node in node_infer.elts
+ ]
+ except StopIteration as e: # pragma: no cover
+ raise InferenceError(node=node, context=context) from e
+ else:
+ class_container = [node_infer]
+ return class_container
+
+
def has_known_bases(klass, context: InferenceContext | None = None) -> bool:
"""Return whether all base classes of a class could be inferred."""
try:
diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py
index 7e51cc1..af7c55b 100644
--- a/astroid/interpreter/_import/spec.py
+++ b/astroid/interpreter/_import/spec.py
@@ -20,8 +20,6 @@
from pathlib import Path
from typing import Literal, NamedTuple, Protocol
-from astroid.const import PY310_PLUS
-
from . import util
@@ -168,56 +166,30 @@
if cached_os_path_isfile(file_path):
return ModuleSpec(name=modname, location=file_path, type=type_)
- # sys.stdlib_module_names was added in Python 3.10
- if PY310_PLUS:
- # If the module name matches a stdlib module name, check whether this is a frozen
- # module. Note that `find_spec` actually imports parent modules, so we want to make
- # sure we only run this code for stuff that can be expected to be frozen. For now
- # this is only stdlib.
- if (modname in sys.stdlib_module_names and not processed) or (
- processed and processed[0] in sys.stdlib_module_names
- ):
- try:
- with warnings.catch_warnings():
- warnings.filterwarnings("ignore", category=Warning)
- spec = importlib.util.find_spec(".".join((*processed, modname)))
- except ValueError:
- spec = None
+ # If the module name matches a stdlib module name, check whether this is a frozen
+ # module. Note that `find_spec` actually imports parent modules, so we want to make
+ # sure we only run this code for stuff that can be expected to be frozen. For now
+ # this is only stdlib.
+ if (modname in sys.stdlib_module_names and not processed) or (
+ processed and processed[0] in sys.stdlib_module_names
+ ):
+ try:
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore", category=Warning)
+ spec = importlib.util.find_spec(".".join((*processed, modname)))
+ except ValueError:
+ spec = None
- if (
- spec
- and spec.loader # type: ignore[comparison-overlap] # noqa: E501
- is importlib.machinery.FrozenImporter
- ):
- return ModuleSpec(
- name=modname,
- location=getattr(spec.loader_state, "filename", None),
- type=ModuleType.PY_FROZEN,
- )
- else:
- # NOTE: This is broken code. It doesn't work on Python 3.13+ where submodules can also
- # be frozen. However, we don't want to worry about this and we don't want to break
- # support for older versions of Python. This is just copy-pasted from the old non
- # working version to at least have no functional behaviour change on <=3.10.
- # It can be removed after 3.10 is no longer supported in favour of the logic above.
- if submodule_path is None: # pylint: disable=else-if-used
- try:
- with warnings.catch_warnings():
- warnings.filterwarnings("ignore", category=UserWarning)
- spec = importlib.util.find_spec(modname)
- if (
- spec
- and spec.loader # type: ignore[comparison-overlap] # noqa: E501
- is importlib.machinery.FrozenImporter
- ):
- # No need for BuiltinImporter; builtins handled above
- return ModuleSpec(
- name=modname,
- location=getattr(spec.loader_state, "filename", None),
- type=ModuleType.PY_FROZEN,
- )
- except ValueError:
- pass
+ if (
+ spec
+ and spec.loader # type: ignore[comparison-overlap] # noqa: E501
+ is importlib.machinery.FrozenImporter
+ ):
+ return ModuleSpec(
+ name=modname,
+ location=getattr(spec.loader_state, "filename", None),
+ type=ModuleType.PY_FROZEN,
+ )
return None
@@ -298,7 +270,7 @@
for entry_path in path:
if entry_path not in sys.path_importer_cache:
try:
- sys.path_importer_cache[entry_path] = zipimport.zipimporter( # type: ignore[assignment]
+ sys.path_importer_cache[entry_path] = zipimport.zipimporter(
entry_path
)
except zipimport.ZipImportError:
@@ -325,6 +297,11 @@
submodule_search_locations=path,
)
+ def contribute_to_path(
+ self, spec: ModuleSpec, processed: list[str]
+ ) -> Sequence[str] | None:
+ return spec.submodule_search_locations
+
class PathSpecFinder(Finder):
"""Finder based on importlib.machinery.PathFinder."""
@@ -391,19 +368,9 @@
modpath: tuple[str, ...],
) -> tuple[Literal[ModuleType.PY_ZIPMODULE], str, str]:
for filepath, importer in _get_zipimporters():
- if PY310_PLUS:
- found = importer.find_spec(modpath[0])
- else:
- found = importer.find_module(modpath[0])
+ found = importer.find_spec(modpath[0])
if found:
- if PY310_PLUS:
- if not importer.find_spec(os.path.sep.join(modpath)):
- raise ImportError(
- "No module named {} in {}/{}".format(
- ".".join(modpath[1:]), filepath, modpath
- )
- )
- elif not importer.find_module(os.path.sep.join(modpath)):
+ if not importer.find_spec(os.path.sep.join(modpath)):
raise ImportError(
"No module named {} in {}/{}".format(
".".join(modpath[1:]), filepath, modpath
diff --git a/astroid/interpreter/_import/util.py b/astroid/interpreter/_import/util.py
index 976fc00..df1aca2 100644
--- a/astroid/interpreter/_import/util.py
+++ b/astroid/interpreter/_import/util.py
@@ -83,7 +83,6 @@
# Repair last_submodule_search_locations
if last_submodule_search_locations:
- # pylint: disable=unsubscriptable-object
last_item = last_submodule_search_locations[-1]
# e.g. for failure example above, add 'a/b' and keep going
# so that find_spec('a.b.c', path=['a', 'a/b']) succeeds
@@ -96,7 +95,10 @@
# But immediately return False if we can detect we are in stdlib
# or external lib (e.g site-packages)
if any(
- any(location.startswith(lib_dir) for lib_dir in STD_AND_EXT_LIB_DIRS)
+ any(
+ str(location).startswith(lib_dir)
+ for lib_dir in STD_AND_EXT_LIB_DIRS
+ )
for location in found_spec.submodule_search_locations
):
return False
diff --git a/astroid/interpreter/dunder_lookup.py b/astroid/interpreter/dunder_lookup.py
index 727c1ad..688971e 100644
--- a/astroid/interpreter/dunder_lookup.py
+++ b/astroid/interpreter/dunder_lookup.py
@@ -12,26 +12,27 @@
As such, the lookup for the special methods is actually simpler than
the dot attribute access.
"""
+
from __future__ import annotations
import itertools
from typing import TYPE_CHECKING
import astroid
+from astroid import nodes
from astroid.exceptions import AttributeInferenceError
if TYPE_CHECKING:
- from astroid import nodes
from astroid.context import InferenceContext
def _lookup_in_mro(node, name) -> list:
attrs = node.locals.get(name, [])
- nodes = itertools.chain.from_iterable(
+ nodes_ = itertools.chain.from_iterable(
ancestor.locals.get(name, []) for ancestor in node.ancestors(recurs=True)
)
- values = list(itertools.chain(attrs, nodes))
+ values = list(itertools.chain(attrs, nodes_))
if not values:
raise AttributeInferenceError(attribute=name, target=node)
@@ -47,13 +48,11 @@
will be returned. Otherwise, `astroid.AttributeInferenceError`
is going to be raised.
"""
- if isinstance(
- node, (astroid.List, astroid.Tuple, astroid.Const, astroid.Dict, astroid.Set)
- ):
+ if isinstance(node, (nodes.List, nodes.Tuple, nodes.Const, nodes.Dict, nodes.Set)):
return _builtin_lookup(node, name)
if isinstance(node, astroid.Instance):
return _lookup_in_mro(node, name)
- if isinstance(node, astroid.ClassDef):
+ if isinstance(node, nodes.ClassDef):
return _class_lookup(node, name, context=context)
raise AttributeInferenceError(attribute=name, target=node)
diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py
index fd8c0c0..148e7d7 100644
--- a/astroid/interpreter/objectmodel.py
+++ b/astroid/interpreter/objectmodel.py
@@ -87,8 +87,11 @@
string = "%(cname)s(%(fields)s)"
alignment = len(cname) + 1
for field in sorted(self.attributes()):
- width = 80 - len(field) - alignment
- lines = pprint.pformat(field, indent=2, width=width).splitlines(True)
+ width = max(80 - len(field) - alignment, 1)
+ try:
+ lines = pprint.pformat(field, indent=2, width=width).splitlines(True)
+ except ValueError:
+ lines = [f"<{type(field).__name__}>"]
inner = [lines[0]]
for line in lines[1:]:
@@ -163,6 +166,33 @@
return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
+ # Base object attributes that return Unknown as fallback placeholders.
+ @property
+ def attr___ne__(self):
+ return node_classes.Unknown(parent=self._instance)
+
+ attr___class__ = attr___ne__
+ attr___delattr__ = attr___ne__
+ attr___dir__ = attr___ne__
+ attr___doc__ = attr___ne__
+ attr___eq__ = attr___ne__
+ attr___format__ = attr___ne__
+ attr___ge__ = attr___ne__
+ attr___getattribute__ = attr___ne__
+ attr___getstate__ = attr___ne__
+ attr___gt__ = attr___ne__
+ attr___hash__ = attr___ne__
+ attr___init_subclass__ = attr___ne__
+ attr___le__ = attr___ne__
+ attr___lt__ = attr___ne__
+ attr___reduce__ = attr___ne__
+ attr___reduce_ex__ = attr___ne__
+ attr___repr__ = attr___ne__
+ attr___setattr__ = attr___ne__
+ attr___sizeof__ = attr___ne__
+ attr___str__ = attr___ne__
+ attr___subclasshook__ = attr___ne__
+
class ModuleModel(ObjectModel):
def _builtins(self):
@@ -228,20 +258,24 @@
@property
def attr___spec__(self):
# No handling for now.
- return node_classes.Unknown()
+ return node_classes.Unknown(parent=self._instance)
@property
def attr___loader__(self):
# No handling for now.
- return node_classes.Unknown()
+ return node_classes.Unknown(parent=self._instance)
@property
def attr___cached__(self):
# No handling for now.
- return node_classes.Unknown()
+ return node_classes.Unknown(parent=self._instance)
class FunctionModel(ObjectModel):
+ def _is_builtin_func(self) -> bool:
+ func = self._instance
+ return isinstance(func.parent, nodes.Module) and not func.root().pure_python
+
@property
def attr___name__(self):
return node_classes.Const(value=self._instance.name, parent=self._instance)
@@ -260,6 +294,8 @@
@property
def attr___defaults__(self):
func = self._instance
+ if self._is_builtin_func():
+ raise AttributeInferenceError(target=func, attribute="__defaults__")
if not func.args.defaults:
return node_classes.Const(value=None, parent=func)
@@ -269,6 +305,10 @@
@property
def attr___annotations__(self):
+ if self._is_builtin_func():
+ raise AttributeInferenceError(
+ target=self._instance, attribute="__annotations__"
+ )
obj = node_classes.Dict(
parent=self._instance,
lineno=self._instance.lineno,
@@ -309,6 +349,8 @@
@property
def attr___dict__(self):
+ if self._is_builtin_func():
+ raise AttributeInferenceError(target=self._instance, attribute="__dict__")
return node_classes.Dict(
parent=self._instance,
lineno=self._instance.lineno,
@@ -317,10 +359,27 @@
end_col_offset=self._instance.end_col_offset,
)
- attr___globals__ = attr___dict__
+ @property
+ def attr___globals__(self):
+ if self._is_builtin_func():
+ raise AttributeInferenceError(
+ target=self._instance, attribute="__globals__"
+ )
+ return node_classes.Dict(
+ parent=self._instance,
+ lineno=self._instance.lineno,
+ col_offset=self._instance.col_offset,
+ end_lineno=self._instance.end_lineno,
+ end_col_offset=self._instance.end_col_offset,
+ )
@property
def attr___kwdefaults__(self):
+ if self._is_builtin_func():
+ raise AttributeInferenceError(
+ target=self._instance, attribute="__kwdefaults__"
+ )
+
def _default_args(args, parent):
for arg in args.kwonlyargs:
try:
@@ -352,6 +411,9 @@
def attr___get__(self):
func = self._instance
+ if self._is_builtin_func():
+ raise AttributeInferenceError(target=func, attribute="__get__")
+
class DescriptorBoundMethod(bases.BoundMethod):
"""Bound method which knows how to understand calling descriptor
binding.
@@ -385,8 +447,12 @@
"Invalid class inferred", target=self, context=context
)
- # For some reason func is a Node that the below
- # code is not expecting
+ # The `func` can already be a Unbound or BoundMethod. If the former, make sure to
+ # wrap as a BoundMethod like we do below when constructing the function from scratch.
+ if isinstance(func, bases.UnboundMethod):
+ yield bases.BoundMethod(proxy=func, bound=cls)
+ return
+
if isinstance(func, bases.BoundMethod):
yield func
return
@@ -427,13 +493,13 @@
we get a new object which has two parameters, *self* and *type*.
"""
nonlocal func
- arguments = astroid.Arguments(
+ arguments = nodes.Arguments(
parent=func.args.parent, vararg=None, kwarg=None
)
positional_or_keyword_params = func.args.args.copy()
positional_or_keyword_params.append(
- astroid.AssignName(
+ nodes.AssignName(
name="type",
lineno=0,
col_offset=0,
@@ -459,30 +525,15 @@
return DescriptorBoundMethod(proxy=self._instance, bound=self._instance)
- # These are here just for completion.
+ # Function-specific attributes.
@property
- def attr___ne__(self):
- return node_classes.Unknown()
+ def attr___call__(self):
+ return node_classes.Unknown(parent=self._instance)
- attr___subclasshook__ = attr___ne__
- attr___str__ = attr___ne__
- attr___sizeof__ = attr___ne__
- attr___setattr___ = attr___ne__
- attr___repr__ = attr___ne__
- attr___reduce__ = attr___ne__
- attr___reduce_ex__ = attr___ne__
- attr___lt__ = attr___ne__
- attr___eq__ = attr___ne__
- attr___gt__ = attr___ne__
- attr___format__ = attr___ne__
- attr___delattr___ = attr___ne__
- attr___getattribute__ = attr___ne__
- attr___hash__ = attr___ne__
- attr___dir__ = attr___ne__
- attr___call__ = attr___ne__
- attr___class__ = attr___ne__
- attr___closure__ = attr___ne__
- attr___code__ = attr___ne__
+ attr___builtins__ = attr___call__
+ attr___closure__ = attr___call__
+ attr___code__ = attr___call__
+ attr___type_params__ = attr___call__
class ClassModel(ObjectModel):
@@ -493,8 +544,8 @@
super().__init__()
@property
- def attr___annotations__(self) -> node_classes.Unkown:
- return node_classes.Unknown()
+ def attr___annotations__(self) -> node_classes.Unknown:
+ return node_classes.Unknown(parent=self._instance)
@property
def attr___module__(self):
@@ -617,7 +668,7 @@
return self._instance._proxied
-class UnboundMethodModel(ObjectModel):
+class UnboundMethodModel(FunctionModel):
@property
def attr___class__(self):
# pylint: disable=import-outside-toplevel; circular import
@@ -770,6 +821,12 @@
return node_classes.Const("")
+class GroupExceptionInstanceModel(ExceptionInstanceModel):
+ @property
+ def attr_exceptions(self) -> nodes.Tuple:
+ return node_classes.Tuple(parent=self._instance)
+
+
class OSErrorInstanceModel(ExceptionInstanceModel):
@property
def attr_filename(self):
@@ -804,6 +861,7 @@
BUILTIN_EXCEPTIONS = {
"builtins.SyntaxError": SyntaxErrorInstanceModel,
+ "builtins.ExceptionGroup": GroupExceptionInstanceModel,
"builtins.ImportError": ImportErrorInstanceModel,
"builtins.UnicodeDecodeError": UnicodeDecodeErrorInstanceModel,
# These are all similar to OSError in terms of attributes
@@ -946,7 +1004,7 @@
def attr_fset(self):
func = self._instance
- def find_setter(func: Property) -> astroid.FunctionDef | None:
+ def find_setter(func: Property) -> nodes.FunctionDef | None:
"""
Given a property, find the corresponding setter function and returns it.
diff --git a/astroid/manager.py b/astroid/manager.py
index 163321b..e232886 100644
--- a/astroid/manager.py
+++ b/astroid/manager.py
@@ -412,7 +412,7 @@
`hook` must be a function that accepts a single argument `modname` which
contains the name of the module or package that could not be imported.
- If `hook` can resolve the import, must return a node of type `astroid.Module`,
+ If `hook` can resolve the import, must return a node of type `nodes.Module`,
otherwise, it must raise `AstroidBuildingError`.
"""
self._failed_import_hooks.append(hook)
diff --git a/astroid/modutils.py b/astroid/modutils.py
index 29d09f8..bb2c7c4 100644
--- a/astroid/modutils.py
+++ b/astroid/modutils.py
@@ -30,15 +30,11 @@
from collections.abc import Callable, Iterable, Sequence
from contextlib import redirect_stderr, redirect_stdout
from functools import lru_cache
+from sys import stdlib_module_names
-from astroid.const import IS_JYTHON, PY310_PLUS
+from astroid.const import IS_JYTHON
from astroid.interpreter._import import spec, util
-if PY310_PLUS:
- from sys import stdlib_module_names
-else:
- from astroid._backport_stdlib_names import stdlib_module_names
-
logger = logging.getLogger(__name__)
@@ -236,6 +232,18 @@
return True
+def _is_subpath(path: str, base: str) -> bool:
+ path = os.path.normcase(os.path.normpath(path))
+ base = os.path.normcase(os.path.normpath(base))
+ if not path.startswith(base):
+ return False
+ return (
+ (len(path) == len(base))
+ or (path[len(base)] == os.path.sep)
+ or (base.endswith(os.path.sep) and path[len(base) - 1] == os.path.sep)
+ )
+
+
def _get_relative_base_path(filename: str, path_to_check: str) -> list[str] | None:
"""Extracts the relative mod path of the file to import from.
@@ -252,19 +260,18 @@
_get_relative_base_path("/a/b/c/d.py", "/a/b") -> ["c","d"]
_get_relative_base_path("/a/b/c/d.py", "/dev") -> None
"""
- importable_path = None
- path_to_check = os.path.normcase(path_to_check)
+ path_to_check = os.path.normcase(os.path.normpath(path_to_check))
+
abs_filename = os.path.abspath(filename)
- if os.path.normcase(abs_filename).startswith(path_to_check):
- importable_path = abs_filename
+ if _is_subpath(abs_filename, path_to_check):
+ base_path = os.path.splitext(abs_filename)[0]
+ relative_base_path = base_path[len(path_to_check) :].lstrip(os.path.sep)
+ return [pkg for pkg in relative_base_path.split(os.sep) if pkg]
real_filename = os.path.realpath(filename)
- if os.path.normcase(real_filename).startswith(path_to_check):
- importable_path = real_filename
-
- if importable_path:
- base_path = os.path.splitext(importable_path)[0]
- relative_base_path = base_path[len(path_to_check) :]
+ if _is_subpath(real_filename, path_to_check):
+ base_path = os.path.splitext(real_filename)[0]
+ relative_base_path = base_path[len(path_to_check) :].lstrip(os.path.sep)
return [pkg for pkg in relative_base_path.split(os.sep) if pkg]
return None
@@ -365,7 +372,7 @@
if modpath[0] == "xml":
# handle _xmlplus
try:
- return _spec_from_modpath(["_xmlplus"] + modpath[1:], path, context)
+ return _spec_from_modpath(["_xmlplus", *modpath[1:]], path, context)
except ImportError:
return _spec_from_modpath(modpath, path, context)
elif modpath == ["os", "path"]:
@@ -489,8 +496,9 @@
"""
filename = os.path.abspath(_path_from_filename(filename))
base, orig_ext = os.path.splitext(filename)
- if orig_ext == ".pyi" and os.path.exists(f"{base}{orig_ext}"):
- return f"{base}{orig_ext}"
+ orig_ext = orig_ext.lstrip(".")
+ if orig_ext not in PY_SOURCE_EXTS and os.path.exists(f"{base}.{orig_ext}"):
+ return f"{base}.{orig_ext}"
for ext in PY_SOURCE_EXTS_STUBS_FIRST if prefer_stubs else PY_SOURCE_EXTS:
source_path = f"{base}.{ext}"
if os.path.exists(source_path):
diff --git a/astroid/nodes/__init__.py b/astroid/nodes/__init__.py
index 7202385..6a67516 100644
--- a/astroid/nodes/__init__.py
+++ b/astroid/nodes/__init__.py
@@ -50,6 +50,7 @@
IfExp,
Import,
ImportFrom,
+ Interpolation,
JoinedStr,
Keyword,
List,
@@ -76,6 +77,7 @@
Slice,
Starred,
Subscript,
+ TemplateStr,
Try,
TryStar,
Tuple,
@@ -247,6 +249,7 @@
"IfExp",
"Import",
"ImportFrom",
+ "Interpolation",
"JoinedStr",
"Keyword",
"Lambda",
@@ -278,6 +281,7 @@
"Slice",
"Starred",
"Subscript",
+ "TemplateStr",
"Try",
"TryStar",
"Tuple",
diff --git a/astroid/nodes/_base_nodes.py b/astroid/nodes/_base_nodes.py
index ca9fcd7..df452cb 100644
--- a/astroid/nodes/_base_nodes.py
+++ b/astroid/nodes/_base_nodes.py
@@ -12,10 +12,9 @@
import itertools
from collections.abc import Callable, Generator, Iterator
from functools import cached_property, lru_cache, partial
-from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union
+from typing import TYPE_CHECKING, Any, ClassVar
from astroid import bases, nodes, util
-from astroid.const import PY310_PLUS
from astroid.context import (
CallContext,
InferenceContext,
@@ -35,10 +34,10 @@
GetFlowFactory = Callable[
[
InferenceResult,
- Optional[InferenceResult],
- Union[nodes.AugAssign, nodes.BinOp],
+ InferenceResult | None,
+ nodes.AugAssign | nodes.BinOp,
InferenceResult,
- Optional[InferenceResult],
+ InferenceResult | None,
InferenceContext,
InferenceContext,
],
@@ -605,8 +604,7 @@
# pylint: disable = too-many-boolean-expressions
if (
- PY310_PLUS
- and op == "|"
+ op == "|"
and (
isinstance(left, (bases.UnionType, nodes.ClassDef))
or (isinstance(left, nodes.Const) and left.value is None)
diff --git a/astroid/nodes/as_string.py b/astroid/nodes/as_string.py
index ceb7410..01007b9 100644
--- a/astroid/nodes/as_string.py
+++ b/astroid/nodes/as_string.py
@@ -14,21 +14,6 @@
if TYPE_CHECKING:
from astroid import objects
- from astroid.nodes import Const
- from astroid.nodes.node_classes import (
- Match,
- MatchAs,
- MatchCase,
- MatchClass,
- MatchMapping,
- MatchOr,
- MatchSequence,
- MatchSingleton,
- MatchStar,
- MatchValue,
- Unknown,
- )
- from astroid.nodes.node_ng import NodeNG
# pylint: disable=unused-argument
@@ -43,11 +28,11 @@
def __init__(self, indent: str = " "):
self.indent: str = indent
- def __call__(self, node: NodeNG) -> str:
+ def __call__(self, node: nodes.NodeNG) -> str:
"""Makes this visitor behave as a simple function"""
return node.accept(self).replace(DOC_NEWLINE, "\n")
- def _docs_dedent(self, doc_node: Const | None) -> str:
+ def _docs_dedent(self, doc_node: nodes.Const | None) -> str:
"""Stop newlines in docs being indented by self._stmt_list"""
if not doc_node:
return ""
@@ -67,7 +52,7 @@
return self.indent + stmts_str.replace("\n", "\n" + self.indent)
def _precedence_parens(
- self, node: NodeNG, child: NodeNG, is_left: bool = True
+ self, node: nodes.NodeNG, child: nodes.NodeNG, is_left: bool = True
) -> str:
"""Wrap child in parens only if required to keep same semantics"""
if self._should_wrap(node, child, is_left):
@@ -75,7 +60,9 @@
return child.accept(self)
- def _should_wrap(self, node: NodeNG, child: NodeNG, is_left: bool) -> bool:
+ def _should_wrap(
+ self, node: nodes.NodeNG, child: nodes.NodeNG, is_left: bool
+ ) -> bool:
"""Wrap child if:
- it has lower precedence
- same precedence with position opposite to associativity direction
@@ -109,34 +96,34 @@
return f"async {self.visit_for(node)}"
def visit_arguments(self, node: nodes.Arguments) -> str:
- """return an astroid.Arguments node as string"""
+ """return an nodes.Arguments node as string"""
return node.format_args()
def visit_assignattr(self, node: nodes.AssignAttr) -> str:
- """return an astroid.AssignAttr node as string"""
+ """return an nodes.AssignAttr node as string"""
return self.visit_attribute(node)
def visit_assert(self, node: nodes.Assert) -> str:
- """return an astroid.Assert node as string"""
+ """return an nodes.Assert node as string"""
if node.fail:
return f"assert {node.test.accept(self)}, {node.fail.accept(self)}"
return f"assert {node.test.accept(self)}"
def visit_assignname(self, node: nodes.AssignName) -> str:
- """return an astroid.AssignName node as string"""
+ """return an nodes.AssignName node as string"""
return node.name
def visit_assign(self, node: nodes.Assign) -> str:
- """return an astroid.Assign node as string"""
+ """return an nodes.Assign node as string"""
lhs = " = ".join(n.accept(self) for n in node.targets)
return f"{lhs} = {node.value.accept(self)}"
def visit_augassign(self, node: nodes.AugAssign) -> str:
- """return an astroid.AugAssign node as string"""
+ """return an nodes.AugAssign node as string"""
return f"{node.target.accept(self)} {node.op} {node.value.accept(self)}"
def visit_annassign(self, node: nodes.AnnAssign) -> str:
- """Return an astroid.AnnAssign node as string"""
+ """Return an nodes.AnnAssign node as string"""
target = node.target.accept(self)
annotation = node.annotation.accept(self)
@@ -145,7 +132,7 @@
return f"{target}: {annotation} = {node.value.accept(self)}"
def visit_binop(self, node: nodes.BinOp) -> str:
- """return an astroid.BinOp node as string"""
+ """return an nodes.BinOp node as string"""
left = self._precedence_parens(node, node.left)
right = self._precedence_parens(node, node.right, is_left=False)
if node.op == "**":
@@ -154,16 +141,16 @@
return f"{left} {node.op} {right}"
def visit_boolop(self, node: nodes.BoolOp) -> str:
- """return an astroid.BoolOp node as string"""
+ """return an nodes.BoolOp node as string"""
values = [f"{self._precedence_parens(node, n)}" for n in node.values]
return (f" {node.op} ").join(values)
def visit_break(self, node: nodes.Break) -> str:
- """return an astroid.Break node as string"""
+ """return an nodes.Break node as string"""
return "break"
def visit_call(self, node: nodes.Call) -> str:
- """return an astroid.Call node as string"""
+ """return an nodes.Call node as string"""
expr_str = self._precedence_parens(node, node.func)
args = [arg.accept(self) for arg in node.args]
if node.keywords:
@@ -174,22 +161,31 @@
args.extend(keywords)
return f"{expr_str}({', '.join(args)})"
+ def _handle_type_params(
+ self, type_params: list[nodes.TypeVar | nodes.ParamSpec | nodes.TypeVarTuple]
+ ) -> str:
+ return (
+ f"[{', '.join(tp.accept(self) for tp in type_params)}]"
+ if type_params
+ else ""
+ )
+
def visit_classdef(self, node: nodes.ClassDef) -> str:
- """return an astroid.ClassDef node as string"""
+ """return an nodes.ClassDef node as string"""
decorate = node.decorators.accept(self) if node.decorators else ""
+ type_params = self._handle_type_params(node.type_params)
args = [n.accept(self) for n in node.bases]
if node._metaclass and not node.has_metaclass_hack():
args.append("metaclass=" + node._metaclass.accept(self))
args += [n.accept(self) for n in node.keywords]
args_str = f"({', '.join(args)})" if args else ""
docs = self._docs_dedent(node.doc_node)
- # TODO: handle type_params
- return "\n\n{}class {}{}:{}\n{}\n".format(
- decorate, node.name, args_str, docs, self._stmt_list(node.body)
+ return "\n\n{}class {}{}{}:{}\n{}\n".format(
+ decorate, node.name, type_params, args_str, docs, self._stmt_list(node.body)
)
def visit_compare(self, node: nodes.Compare) -> str:
- """return an astroid.Compare node as string"""
+ """return an nodes.Compare node as string"""
rhs_str = " ".join(
f"{op} {self._precedence_parens(node, expr, is_left=False)}"
for op, expr in node.ops
@@ -197,39 +193,39 @@
return f"{self._precedence_parens(node, node.left)} {rhs_str}"
def visit_comprehension(self, node: nodes.Comprehension) -> str:
- """return an astroid.Comprehension node as string"""
+ """return an nodes.Comprehension node as string"""
ifs = "".join(f" if {n.accept(self)}" for n in node.ifs)
generated = f"for {node.target.accept(self)} in {node.iter.accept(self)}{ifs}"
return f"{'async ' if node.is_async else ''}{generated}"
def visit_const(self, node: nodes.Const) -> str:
- """return an astroid.Const node as string"""
+ """return an nodes.Const node as string"""
if node.value is Ellipsis:
return "..."
return repr(node.value)
def visit_continue(self, node: nodes.Continue) -> str:
- """return an astroid.Continue node as string"""
+ """return an nodes.Continue node as string"""
return "continue"
def visit_delete(self, node: nodes.Delete) -> str:
- """return an astroid.Delete node as string"""
+ """return an nodes.Delete node as string"""
return f"del {', '.join(child.accept(self) for child in node.targets)}"
def visit_delattr(self, node: nodes.DelAttr) -> str:
- """return an astroid.DelAttr node as string"""
+ """return an nodes.DelAttr node as string"""
return self.visit_attribute(node)
def visit_delname(self, node: nodes.DelName) -> str:
- """return an astroid.DelName node as string"""
+ """return an nodes.DelName node as string"""
return node.name
def visit_decorators(self, node: nodes.Decorators) -> str:
- """return an astroid.Decorators node as string"""
+ """return an nodes.Decorators node as string"""
return "@%s\n" % "\n@".join(item.accept(self) for item in node.nodes)
def visit_dict(self, node: nodes.Dict) -> str:
- """return an astroid.Dict node as string"""
+ """return an nodes.Dict node as string"""
return "{%s}" % ", ".join(self._visit_dict(node))
def _visit_dict(self, node: nodes.Dict) -> Iterator[str]:
@@ -246,7 +242,7 @@
return "**"
def visit_dictcomp(self, node: nodes.DictComp) -> str:
- """return an astroid.DictComp node as string"""
+ """return an nodes.DictComp node as string"""
return "{{{}: {} {}}}".format(
node.key.accept(self),
node.value.accept(self),
@@ -254,7 +250,7 @@
)
def visit_expr(self, node: nodes.Expr) -> str:
- """return an astroid.Expr node as string"""
+ """return an nodes.Expr node as string"""
return node.value.accept(self)
def visit_emptynode(self, node: nodes.EmptyNode) -> str:
@@ -279,7 +275,7 @@
return ""
def visit_for(self, node: nodes.For) -> str:
- """return an astroid.For node as string"""
+ """return an nodes.For node as string"""
fors = "for {} in {}:\n{}".format(
node.target.accept(self), node.iter.accept(self), self._stmt_list(node.body)
)
@@ -288,7 +284,7 @@
return fors
def visit_importfrom(self, node: nodes.ImportFrom) -> str:
- """return an astroid.ImportFrom node as string"""
+ """return an nodes.ImportFrom node as string"""
return "from {} import {}".format(
"." * (node.level or 0) + node.modname, _import_string(node.names)
)
@@ -334,17 +330,18 @@
def handle_functiondef(self, node: nodes.FunctionDef, keyword: str) -> str:
"""return a (possibly async) function definition node as string"""
decorate = node.decorators.accept(self) if node.decorators else ""
+ type_params = self._handle_type_params(node.type_params)
docs = self._docs_dedent(node.doc_node)
trailer = ":"
if node.returns:
return_annotation = " -> " + node.returns.as_string()
trailer = return_annotation + ":"
- # TODO: handle type_params
- def_format = "\n%s%s %s(%s)%s%s\n%s"
+ def_format = "\n%s%s %s%s(%s)%s%s\n%s"
return def_format % (
decorate,
keyword,
node.name,
+ type_params,
node.args.accept(self),
trailer,
docs,
@@ -352,15 +349,15 @@
)
def visit_functiondef(self, node: nodes.FunctionDef) -> str:
- """return an astroid.FunctionDef node as string"""
+ """return an nodes.FunctionDef node as string"""
return self.handle_functiondef(node, "def")
def visit_asyncfunctiondef(self, node: nodes.AsyncFunctionDef) -> str:
- """return an astroid.AsyncFunction node as string"""
+ """return an nodes.AsyncFunction node as string"""
return self.handle_functiondef(node, "async def")
def visit_generatorexp(self, node: nodes.GeneratorExp) -> str:
- """return an astroid.GeneratorExp node as string"""
+ """return an nodes.GeneratorExp node as string"""
return "({} {})".format(
node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
)
@@ -368,7 +365,7 @@
def visit_attribute(
self, node: nodes.Attribute | nodes.AssignAttr | nodes.DelAttr
) -> str:
- """return an astroid.Attribute node as string"""
+ """return an nodes.Attribute node as string"""
try:
left = self._precedence_parens(node, node.expr)
except RecursionError:
@@ -383,11 +380,11 @@
return f"{left}.{node.attrname}"
def visit_global(self, node: nodes.Global) -> str:
- """return an astroid.Global node as string"""
+ """return an nodes.Global node as string"""
return f"global {', '.join(node.names)}"
def visit_if(self, node: nodes.If) -> str:
- """return an astroid.If node as string"""
+ """return an nodes.If node as string"""
ifs = [f"if {node.test.accept(self)}:\n{self._stmt_list(node.body)}"]
if node.has_elif_block():
ifs.append(f"el{self._stmt_list(node.orelse, indent=False)}")
@@ -396,7 +393,7 @@
return "\n".join(ifs)
def visit_ifexp(self, node: nodes.IfExp) -> str:
- """return an astroid.IfExp node as string"""
+ """return an nodes.IfExp node as string"""
return "{} if {} else {}".format(
self._precedence_parens(node, node.body, is_left=True),
self._precedence_parens(node, node.test, is_left=True),
@@ -404,17 +401,17 @@
)
def visit_import(self, node: nodes.Import) -> str:
- """return an astroid.Import node as string"""
+ """return an nodes.Import node as string"""
return f"import {_import_string(node.names)}"
def visit_keyword(self, node: nodes.Keyword) -> str:
- """return an astroid.Keyword node as string"""
+ """return an nodes.Keyword node as string"""
if node.arg is None:
return f"**{node.value.accept(self)}"
return f"{node.arg}={node.value.accept(self)}"
def visit_lambda(self, node: nodes.Lambda) -> str:
- """return an astroid.Lambda node as string"""
+ """return an nodes.Lambda node as string"""
args = node.args.accept(self)
body = node.body.accept(self)
if args:
@@ -423,22 +420,22 @@
return f"lambda: {body}"
def visit_list(self, node: nodes.List) -> str:
- """return an astroid.List node as string"""
+ """return an nodes.List node as string"""
return f"[{', '.join(child.accept(self) for child in node.elts)}]"
def visit_listcomp(self, node: nodes.ListComp) -> str:
- """return an astroid.ListComp node as string"""
+ """return an nodes.ListComp node as string"""
return "[{} {}]".format(
node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
)
def visit_module(self, node: nodes.Module) -> str:
- """return an astroid.Module node as string"""
+ """return an nodes.Module node as string"""
docs = f'"""{node.doc_node.value}"""\n\n' if node.doc_node else ""
return docs + "\n".join(n.accept(self) for n in node.body) + "\n\n"
def visit_name(self, node: nodes.Name) -> str:
- """return an astroid.Name node as string"""
+ """return an nodes.Name node as string"""
return node.name
def visit_namedexpr(self, node: nodes.NamedExpr) -> str:
@@ -448,15 +445,18 @@
return f"{target} := {value}"
def visit_nonlocal(self, node: nodes.Nonlocal) -> str:
- """return an astroid.Nonlocal node as string"""
+ """return an nodes.Nonlocal node as string"""
return f"nonlocal {', '.join(node.names)}"
def visit_paramspec(self, node: nodes.ParamSpec) -> str:
- """return an astroid.ParamSpec node as string"""
- return node.name.accept(self)
+ """return an nodes.ParamSpec node as string"""
+ default_value_str = (
+ f" = {node.default_value.accept(self)}" if node.default_value else ""
+ )
+ return f"**{node.name.accept(self)}{default_value_str}"
def visit_pass(self, node: nodes.Pass) -> str:
- """return an astroid.Pass node as string"""
+ """return an nodes.Pass node as string"""
return "pass"
def visit_partialfunction(self, node: objects.PartialFunction) -> str:
@@ -464,7 +464,7 @@
return self.visit_functiondef(node)
def visit_raise(self, node: nodes.Raise) -> str:
- """return an astroid.Raise node as string"""
+ """return an nodes.Raise node as string"""
if node.exc:
if node.cause:
return f"raise {node.exc.accept(self)} from {node.cause.accept(self)}"
@@ -472,7 +472,7 @@
return "raise"
def visit_return(self, node: nodes.Return) -> str:
- """return an astroid.Return node as string"""
+ """return an nodes.Return node as string"""
if node.is_tuple_return() and len(node.value.elts) > 1:
elts = [child.accept(self) for child in node.value.elts]
return f"return {', '.join(elts)}"
@@ -483,17 +483,17 @@
return "return"
def visit_set(self, node: nodes.Set) -> str:
- """return an astroid.Set node as string"""
+ """return an nodes.Set node as string"""
return "{%s}" % ", ".join(child.accept(self) for child in node.elts)
def visit_setcomp(self, node: nodes.SetComp) -> str:
- """return an astroid.SetComp node as string"""
+ """return an nodes.SetComp node as string"""
return "{{{} {}}}".format(
node.elt.accept(self), " ".join(n.accept(self) for n in node.generators)
)
def visit_slice(self, node: nodes.Slice) -> str:
- """return an astroid.Slice node as string"""
+ """return an nodes.Slice node as string"""
lower = node.lower.accept(self) if node.lower else ""
upper = node.upper.accept(self) if node.upper else ""
step = node.step.accept(self) if node.step else ""
@@ -502,7 +502,7 @@
return f"{lower}:{upper}"
def visit_subscript(self, node: nodes.Subscript) -> str:
- """return an astroid.Subscript node as string"""
+ """return an nodes.Subscript node as string"""
idx = node.slice
if idx.__class__.__name__.lower() == "index":
idx = idx.value
@@ -514,7 +514,7 @@
return f"{self._precedence_parens(node, node.value)}[{idxstr}]"
def visit_try(self, node: nodes.Try) -> str:
- """return an astroid.Try node as string"""
+ """return an nodes.Try node as string"""
trys = [f"try:\n{self._stmt_list(node.body)}"]
for handler in node.handlers:
trys.append(handler.accept(self))
@@ -525,7 +525,7 @@
return "\n".join(trys)
def visit_trystar(self, node: nodes.TryStar) -> str:
- """return an astroid.TryStar node as string"""
+ """return an nodes.TryStar node as string"""
trys = [f"try:\n{self._stmt_list(node.body)}"]
for handler in node.handlers:
trys.append(handler.accept(self))
@@ -536,25 +536,33 @@
return "\n".join(trys)
def visit_tuple(self, node: nodes.Tuple) -> str:
- """return an astroid.Tuple node as string"""
+ """return an nodes.Tuple node as string"""
if len(node.elts) == 1:
return f"({node.elts[0].accept(self)}, )"
return f"({', '.join(child.accept(self) for child in node.elts)})"
def visit_typealias(self, node: nodes.TypeAlias) -> str:
- """return an astroid.TypeAlias node as string"""
- return node.name.accept(self) if node.name else "_"
+ """return an nodes.TypeAlias node as string"""
+ type_params = self._handle_type_params(node.type_params)
+ return f"type {node.name.accept(self)}{type_params} = {node.value.accept(self)}"
def visit_typevar(self, node: nodes.TypeVar) -> str:
- """return an astroid.TypeVar node as string"""
- return node.name.accept(self) if node.name else "_"
+ """return an nodes.TypeVar node as string"""
+ bound_str = f": {node.bound.accept(self)}" if node.bound else ""
+ default_value_str = (
+ f" = {node.default_value.accept(self)}" if node.default_value else ""
+ )
+ return f"{node.name.accept(self)}{bound_str}{default_value_str}"
def visit_typevartuple(self, node: nodes.TypeVarTuple) -> str:
- """return an astroid.TypeVarTuple node as string"""
- return "*" + node.name.accept(self) if node.name else ""
+ """return an nodes.TypeVarTuple node as string"""
+ default_value_str = (
+ f" = {node.default_value.accept(self)}" if node.default_value else ""
+ )
+ return f"*{node.name.accept(self)}{default_value_str}"
def visit_unaryop(self, node: nodes.UnaryOp) -> str:
- """return an astroid.UnaryOp node as string"""
+ """return an nodes.UnaryOp node as string"""
if node.op == "not":
operator = "not "
else:
@@ -562,14 +570,14 @@
return f"{operator}{self._precedence_parens(node, node.operand)}"
def visit_while(self, node: nodes.While) -> str:
- """return an astroid.While node as string"""
+ """return an nodes.While node as string"""
whiles = f"while {node.test.accept(self)}:\n{self._stmt_list(node.body)}"
if node.orelse:
whiles = f"{whiles}\nelse:\n{self._stmt_list(node.orelse)}"
return whiles
def visit_with(self, node: nodes.With) -> str: # 'with' without 'as' is possible
- """return an astroid.With node as string"""
+ """return an nodes.With node as string"""
items = ", ".join(
f"{expr.accept(self)}" + ((v and f" as {v.accept(self)}") or "")
for expr, v in node.items
@@ -586,7 +594,7 @@
return f"({expr})"
def visit_yieldfrom(self, node: nodes.YieldFrom) -> str:
- """Return an astroid.YieldFrom node as string."""
+ """Return an nodes.YieldFrom node as string."""
yi_val = (" " + node.value.accept(self)) if node.value else ""
expr = "yield from" + yi_val
if node.parent.is_statement:
@@ -598,35 +606,35 @@
"""return Starred node as string"""
return "*" + node.value.accept(self)
- def visit_match(self, node: Match) -> str:
- """Return an astroid.Match node as string."""
+ def visit_match(self, node: nodes.Match) -> str:
+ """Return an nodes.Match node as string."""
return f"match {node.subject.accept(self)}:\n{self._stmt_list(node.cases)}"
- def visit_matchcase(self, node: MatchCase) -> str:
- """Return an astroid.MatchCase node as string."""
+ def visit_matchcase(self, node: nodes.MatchCase) -> str:
+ """Return an nodes.MatchCase node as string."""
guard_str = f" if {node.guard.accept(self)}" if node.guard else ""
return (
f"case {node.pattern.accept(self)}{guard_str}:\n"
f"{self._stmt_list(node.body)}"
)
- def visit_matchvalue(self, node: MatchValue) -> str:
- """Return an astroid.MatchValue node as string."""
+ def visit_matchvalue(self, node: nodes.MatchValue) -> str:
+ """Return an nodes.MatchValue node as string."""
return node.value.accept(self)
@staticmethod
- def visit_matchsingleton(node: MatchSingleton) -> str:
- """Return an astroid.MatchSingleton node as string."""
+ def visit_matchsingleton(node: nodes.MatchSingleton) -> str:
+ """Return an nodes.MatchSingleton node as string."""
return str(node.value)
- def visit_matchsequence(self, node: MatchSequence) -> str:
- """Return an astroid.MatchSequence node as string."""
+ def visit_matchsequence(self, node: nodes.MatchSequence) -> str:
+ """Return an nodes.MatchSequence node as string."""
if node.patterns is None:
return "[]"
return f"[{', '.join(p.accept(self) for p in node.patterns)}]"
- def visit_matchmapping(self, node: MatchMapping) -> str:
- """Return an astroid.MatchMapping node as string."""
+ def visit_matchmapping(self, node: nodes.MatchMapping) -> str:
+ """Return an nodes..MatchMapping node as string."""
mapping_strings: list[str] = []
if node.keys and node.patterns:
mapping_strings.extend(
@@ -637,8 +645,8 @@
mapping_strings.append(f"**{node.rest.accept(self)}")
return f"{'{'}{', '.join(mapping_strings)}{'}'}"
- def visit_matchclass(self, node: MatchClass) -> str:
- """Return an astroid.MatchClass node as string."""
+ def visit_matchclass(self, node: nodes.MatchClass) -> str:
+ """Return an nodes..MatchClass node as string."""
if node.cls is None:
raise AssertionError(f"{node} does not have a 'cls' node")
class_strings: list[str] = []
@@ -649,29 +657,53 @@
class_strings.append(f"{attr}={pattern.accept(self)}")
return f"{node.cls.accept(self)}({', '.join(class_strings)})"
- def visit_matchstar(self, node: MatchStar) -> str:
- """Return an astroid.MatchStar node as string."""
+ def visit_matchstar(self, node: nodes.MatchStar) -> str:
+ """Return an nodes..MatchStar node as string."""
return f"*{node.name.accept(self) if node.name else '_'}"
- def visit_matchas(self, node: MatchAs) -> str:
- """Return an astroid.MatchAs node as string."""
- # pylint: disable=import-outside-toplevel
- # Prevent circular dependency
- from astroid.nodes.node_classes import MatchClass, MatchMapping, MatchSequence
-
- if isinstance(node.parent, (MatchSequence, MatchMapping, MatchClass)):
+ def visit_matchas(self, node: nodes.MatchAs) -> str:
+ """Return an nodes..MatchAs node as string."""
+ if isinstance(
+ node.parent, (nodes.MatchSequence, nodes.MatchMapping, nodes.MatchClass)
+ ):
return node.name.accept(self) if node.name else "_"
return (
f"{node.pattern.accept(self) if node.pattern else '_'}"
f"{f' as {node.name.accept(self)}' if node.name else ''}"
)
- def visit_matchor(self, node: MatchOr) -> str:
- """Return an astroid.MatchOr node as string."""
+ def visit_matchor(self, node: nodes.MatchOr) -> str:
+ """Return an nodes.MatchOr node as string."""
if node.patterns is None:
raise AssertionError(f"{node} does not have pattern nodes")
return " | ".join(p.accept(self) for p in node.patterns)
+ def visit_templatestr(self, node: nodes.TemplateStr) -> str:
+ """Return an nodes.TemplateStr node as string."""
+ string = ""
+ for value in node.values:
+ match value:
+ case nodes.Interpolation():
+ string += "{" + value.accept(self) + "}"
+ case _:
+ string += value.accept(self)[1:-1]
+ for quote in ("'", '"', '"""', "'''"):
+ if quote not in string:
+ break
+ return "t" + quote + string + quote
+
+ def visit_interpolation(self, node: nodes.Interpolation) -> str:
+ """Return an nodes.Interpolation node as string."""
+ result = f"{node.str}"
+ if node.conversion and node.conversion >= 0:
+ # e.g. if node.conversion == 114: result += "!r"
+ result += "!" + chr(node.conversion)
+ if node.format_spec:
+ # The format spec is itself a JoinedString, i.e. an f-string
+ # We strip the f and quotes of the ends
+ result += ":" + node.format_spec.accept(self)[2:-1]
+ return result
+
# These aren't for real AST nodes, but for inference objects.
def visit_frozenset(self, node: objects.FrozenSet) -> str:
@@ -689,7 +721,7 @@
def visit_evaluatedobject(self, node: nodes.EvaluatedObject) -> str:
return node.original.accept(self)
- def visit_unknown(self, node: Unknown) -> str:
+ def visit_unknown(self, node: nodes.Unknown) -> str:
return str(node)
diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py
index ebfd5ff..af2131c 100644
--- a/astroid/nodes/node_classes.py
+++ b/astroid/nodes/node_classes.py
@@ -15,18 +15,11 @@
import warnings
from collections.abc import Callable, Generator, Iterable, Iterator, Mapping
from functools import cached_property
-from typing import (
- TYPE_CHECKING,
- Any,
- ClassVar,
- Literal,
- Optional,
- Union,
-)
+from typing import TYPE_CHECKING, Any, ClassVar, Literal, Union
from astroid import decorators, protocols, util
from astroid.bases import Instance, _infer_stmts
-from astroid.const import _EMPTY_OBJECT_MARKER, Context
+from astroid.const import _EMPTY_OBJECT_MARKER, PY314_PLUS, Context
from astroid.context import CallContext, InferenceContext, copy_context
from astroid.exceptions import (
AstroidBuildingError,
@@ -62,6 +55,7 @@
if TYPE_CHECKING:
from astroid import nodes
from astroid.nodes import LocalsDictNodeNG
+ from astroid.nodes.node_ng import FrameType
def _is_const(value) -> bool:
@@ -71,23 +65,24 @@
_NodesT = typing.TypeVar("_NodesT", bound=NodeNG)
_BadOpMessageT = typing.TypeVar("_BadOpMessageT", bound=util.BadOperationMessage)
+# pylint: disable-next=consider-alternative-union-syntax
AssignedStmtsPossibleNode = Union["List", "Tuple", "AssignName", "AssignAttr", None]
AssignedStmtsCall = Callable[
[
_NodesT,
AssignedStmtsPossibleNode,
- Optional[InferenceContext],
- Optional[list[int]],
+ InferenceContext | None,
+ list[int] | None,
],
Any,
]
InferBinaryOperation = Callable[
- [_NodesT, Optional[InferenceContext]],
- Generator[Union[InferenceResult, _BadOpMessageT]],
+ [_NodesT, InferenceContext | None],
+ Generator[InferenceResult | _BadOpMessageT],
]
InferLHS = Callable[
- [_NodesT, Optional[InferenceContext]],
- Generator[InferenceResult, None, Optional[InferenceErrorInfo]],
+ [_NodesT, InferenceContext | None],
+ Generator[InferenceResult, None, InferenceErrorInfo | None],
]
InferUnaryOp = Callable[[_NodesT, str], ConstFactoryResult]
@@ -1026,9 +1021,26 @@
if elt is not None:
yield elt
+ def get_annotations(self) -> Iterator[nodes.NodeNG]:
+ """Iterate over all annotations nodes."""
+ for elt in self.posonlyargs_annotations:
+ if elt is not None:
+ yield elt
+ for elt in self.annotations:
+ if elt is not None:
+ yield elt
+ if self.varargannotation is not None:
+ yield self.varargannotation
+
+ for elt in self.kwonlyargs_annotations:
+ if elt is not None:
+ yield elt
+ if self.kwargannotation is not None:
+ yield self.kwargannotation
+
@decorators.raise_if_nothing_inferred
def _infer(
- self: nodes.Arguments, context: InferenceContext | None = None, **kwargs: Any
+ self, context: InferenceContext | None = None, **kwargs: Any
) -> Generator[InferenceResult]:
# pylint: disable-next=import-outside-toplevel
from astroid.protocols import _arguments_infer_argname
@@ -1447,7 +1459,7 @@
@decorators.raise_if_nothing_inferred
@decorators.path_wrapper
def _infer(
- self: nodes.AugAssign, context: InferenceContext | None = None, **kwargs: Any
+ self, context: InferenceContext | None = None, **kwargs: Any
) -> Generator[InferenceResult]:
return self._filter_operation_errors(
self._infer_augassign, context, util.BadBinaryOperationMessage
@@ -1562,7 +1574,7 @@
@decorators.yes_if_nothing_inferred
@decorators.path_wrapper
def _infer(
- self: nodes.BinOp, context: InferenceContext | None = None, **kwargs: Any
+ self, context: InferenceContext | None = None, **kwargs: Any
) -> Generator[InferenceResult]:
return self._filter_operation_errors(
self._infer_binop, context, util.BadBinaryOperationMessage
@@ -1639,7 +1651,7 @@
@decorators.raise_if_nothing_inferred
@decorators.path_wrapper
def _infer(
- self: nodes.BoolOp, context: InferenceContext | None = None, **kwargs: Any
+ self, context: InferenceContext | None = None, **kwargs: Any
) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
"""Infer a boolean operation (and / or / not).
@@ -1857,7 +1869,7 @@
# TODO: move to util?
@staticmethod
def _to_literal(node: SuccessfulInferenceResult) -> Any:
- # Can raise SyntaxError or ValueError from ast.literal_eval
+ # Can raise SyntaxError, ValueError, or TypeError from ast.literal_eval
# Can raise AttributeError from node.as_string() as not all nodes have a visitor
# Is this the stupidest idea or the simplest idea?
return ast.literal_eval(node.as_string())
@@ -1893,7 +1905,7 @@
try:
left, right = self._to_literal(left), self._to_literal(right)
- except (SyntaxError, ValueError, AttributeError):
+ except (SyntaxError, ValueError, AttributeError, TypeError):
return util.Uninferable
try:
@@ -2139,7 +2151,11 @@
message="Type error {error!r}", node=self, index=index, context=context
) from exc
- raise AstroidTypeError(f"{self!r} (value={self.value})")
+ try:
+ value_str = str(self.value)
+ except ValueError:
+ value_str = f"<{type(self.value).__name__} (too large to display)>"
+ raise AstroidTypeError(f"{self!r} (value={value_str})")
def has_dynamic_getattr(self) -> bool:
"""Check if the node has a custom __getattr__ or __getattribute__.
@@ -2172,8 +2188,12 @@
"""Determine the boolean value of this node.
:returns: The boolean value of this node.
- :rtype: bool
+ :rtype: bool or Uninferable
"""
+ # bool(NotImplemented) is deprecated; it raises TypeError starting from Python 3.14
+ # and returns True for versions under 3.14
+ if self.value is NotImplemented:
+ return util.Uninferable if PY314_PLUS else True
return bool(self.value)
def _infer(
@@ -2224,13 +2244,22 @@
:returns: The first parent scope node.
"""
- # skip the function node to go directly to the upper level scope
+ # skip the function or class node to go directly to the upper level scope
if not self.parent:
raise ParentMissingError(target=self)
if not self.parent.parent:
raise ParentMissingError(target=self.parent)
return self.parent.parent.scope()
+ def frame(self) -> FrameType:
+ """The first parent node defining a new frame."""
+ # skip the function or class node to go directly to the upper level frame
+ if not self.parent:
+ raise ParentMissingError(target=self)
+ if not self.parent.parent:
+ raise ParentMissingError(target=self.parent)
+ return self.parent.parent.frame()
+
def get_children(self):
yield from self.nodes
@@ -3110,28 +3139,37 @@
to inferring both branches. Otherwise, we infer either branch
depending on the condition.
"""
- both_branches = False
+
# We use two separate contexts for evaluating lhs and rhs because
# evaluating lhs may leave some undesired entries in context.path
# which may not let us infer right value of rhs.
-
context = context or InferenceContext()
lhs_context = copy_context(context)
rhs_context = copy_context(context)
+
+ # Infer bool condition. Stop inferring if in doubt and fallback to
+ # evaluating both branches.
+ condition: bool | None = None
try:
- test = next(self.test.infer(context=context.clone()))
- except (InferenceError, StopIteration):
- both_branches = True
- else:
- if not isinstance(test, util.UninferableBase):
- if test.bool_value():
- yield from self.body.infer(context=lhs_context)
- else:
- yield from self.orelse.infer(context=rhs_context)
- else:
- both_branches = True
- if both_branches:
+ for test in self.test.infer(context=context.clone()):
+ if isinstance(test, util.UninferableBase):
+ condition = None
+ break
+ test_bool_value = test.bool_value()
+ if isinstance(test_bool_value, util.UninferableBase):
+ condition = None
+ break
+ if condition is None:
+ condition = test_bool_value
+ elif test_bool_value != condition:
+ condition = None
+ break
+ except InferenceError:
+ condition = None
+
+ if condition is True or condition is None:
yield from self.body.infer(context=lhs_context)
+ if condition is False or condition is None:
yield from self.orelse.infer(context=rhs_context)
@@ -3389,9 +3427,9 @@
<ParamSpec l.1 at 0x7f23b2e4e198>
"""
- _astroid_fields = ("name",)
-
+ _astroid_fields = ("name", "default_value")
name: AssignName
+ default_value: NodeNG | None
def __init__(
self,
@@ -3410,8 +3448,9 @@
parent=parent,
)
- def postinit(self, *, name: AssignName) -> None:
+ def postinit(self, *, name: AssignName, default_value: NodeNG | None) -> None:
self.name = name
+ self.default_value = default_value
def _infer(
self, context: InferenceContext | None = None, **kwargs: Any
@@ -4147,10 +4186,10 @@
<TypeVar l.1 at 0x7f23b2e4e198>
"""
- _astroid_fields = ("name", "bound")
-
+ _astroid_fields = ("name", "bound", "default_value")
name: AssignName
bound: NodeNG | None
+ default_value: NodeNG | None
def __init__(
self,
@@ -4169,9 +4208,16 @@
parent=parent,
)
- def postinit(self, *, name: AssignName, bound: NodeNG | None) -> None:
+ def postinit(
+ self,
+ *,
+ name: AssignName,
+ bound: NodeNG | None,
+ default_value: NodeNG | None = None,
+ ) -> None:
self.name = name
self.bound = bound
+ self.default_value = default_value
def _infer(
self, context: InferenceContext | None = None, **kwargs: Any
@@ -4193,9 +4239,9 @@
<TypeVarTuple l.1 at 0x7f23b2e4e198>
"""
- _astroid_fields = ("name",)
-
+ _astroid_fields = ("name", "default_value")
name: AssignName
+ default_value: NodeNG | None
def __init__(
self,
@@ -4214,8 +4260,11 @@
parent=parent,
)
- def postinit(self, *, name: AssignName) -> None:
+ def postinit(
+ self, *, name: AssignName, default_value: NodeNG | None = None
+ ) -> None:
self.name = name
+ self.default_value = default_value
def _infer(
self, context: InferenceContext | None = None, **kwargs: Any
@@ -4306,7 +4355,7 @@
return super().op_precedence()
def _infer_unaryop(
- self: nodes.UnaryOp, context: InferenceContext | None = None, **kwargs: Any
+ self, context: InferenceContext | None = None, **kwargs: Any
) -> Generator[
InferenceResult | util.BadUnaryOperationMessage, None, InferenceErrorInfo
]:
@@ -4372,7 +4421,7 @@
@decorators.raise_if_nothing_inferred
@decorators.path_wrapper
def _infer(
- self: nodes.UnaryOp, context: InferenceContext | None = None, **kwargs: Any
+ self, context: InferenceContext | None = None, **kwargs: Any
) -> Generator[InferenceResult, None, InferenceErrorInfo]:
"""Infer what an UnaryOp should return when evaluated."""
yield from self._filter_operation_errors(
@@ -4695,6 +4744,9 @@
uninferable_already_generated = True
continue
for value in self.value.infer(context, **kwargs):
+ if value is util.Uninferable:
+ yield util.Uninferable
+ return
value_to_format = value
if isinstance(value, Const):
value_to_format = value.value
@@ -4708,8 +4760,9 @@
end_col_offset=self.end_col_offset,
)
continue
- except (ValueError, TypeError):
- # happens when format_spec.value is invalid
+ except (ValueError, TypeError, MemoryError):
+ # ValueError/TypeError: invalid format spec
+ # MemoryError: format spec with huge width (e.g. f'{0:11111111111}')
yield util.Uninferable
uninferable_already_generated = True
continue
@@ -4898,7 +4951,7 @@
See astroid/protocols.py for actual implementation.
"""
- def frame(self) -> nodes.FunctionDef | nodes.Module | nodes.ClassDef | nodes.Lambda:
+ def frame(self) -> FrameType:
"""The first parent frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
@@ -4954,18 +5007,19 @@
class Unknown(_base_nodes.AssignTypeNode):
"""This node represents a node in a constructed AST where
- introspection is not possible. At the moment, it's only used in
- the args attribute of FunctionDef nodes where function signature
- introspection failed.
+ introspection is not possible.
+
+ Used in the args attribute of FunctionDef nodes where function signature
+ introspection failed, and as a placeholder in ObjectModel.
"""
name = "Unknown"
def __init__(
self,
+ parent: NodeNG,
lineno: None = None,
col_offset: None = None,
- parent: None = None,
*,
end_lineno: None = None,
end_col_offset: None = None,
@@ -4986,6 +5040,9 @@
yield util.Uninferable
+UNATTACHED_UNKNOWN = Unknown(parent=SYNTHETIC_ROOT)
+
+
class EvaluatedObject(NodeNG):
"""Contains an object that has already been inferred
@@ -5487,6 +5544,114 @@
self.patterns = patterns
+class TemplateStr(NodeNG):
+ """Class representing an :class:`ast.TemplateStr` node.
+
+ >>> import astroid
+ >>> node = astroid.extract_node('t"{name} finished {place!s}"')
+ >>> node
+ <TemplateStr l.1 at 0x103b7aa50>
+ """
+
+ _astroid_fields = ("values",)
+
+ def __init__(
+ self,
+ lineno: int | None = None,
+ col_offset: int | None = None,
+ parent: NodeNG | None = None,
+ *,
+ end_lineno: int | None = None,
+ end_col_offset: int | None = None,
+ ) -> None:
+ self.values: list[NodeNG]
+ super().__init__(
+ lineno=lineno,
+ col_offset=col_offset,
+ end_lineno=end_lineno,
+ end_col_offset=end_col_offset,
+ parent=parent,
+ )
+
+ def postinit(self, *, values: list[NodeNG]) -> None:
+ self.values = values
+
+ def get_children(self) -> Iterator[NodeNG]:
+ yield from self.values
+
+
+class Interpolation(NodeNG):
+ """Class representing an :class:`ast.Interpolation` node.
+
+ >>> import astroid
+ >>> node = astroid.extract_node('t"{name} finished {place!s}"')
+ >>> node
+ <TemplateStr l.1 at 0x103b7aa50>
+ >>> node.values[0]
+ <Interpolation l.1 at 0x103b7acf0>
+ >>> node.values[2]
+ <Interpolation l.1 at 0x10411e5d0>
+ """
+
+ _astroid_fields = ("value", "format_spec")
+ _other_fields = ("str", "conversion")
+
+ def __init__(
+ self,
+ lineno: int | None = None,
+ col_offset: int | None = None,
+ parent: NodeNG | None = None,
+ *,
+ end_lineno: int | None = None,
+ end_col_offset: int | None = None,
+ ) -> None:
+ self.value: NodeNG
+ """Any expression node."""
+
+ self.str: str
+ """Text of the interpolation expression."""
+
+ self.conversion: int
+ """The type of formatting to be applied to the value.
+
+ .. seealso::
+ :class:`ast.Interpolation`
+ """
+
+ self.format_spec: JoinedStr | None = None
+ """The formatting to be applied to the value.
+
+ .. seealso::
+ :class:`ast.Interpolation`
+ """
+
+ super().__init__(
+ lineno=lineno,
+ col_offset=col_offset,
+ end_lineno=end_lineno,
+ end_col_offset=end_col_offset,
+ parent=parent,
+ )
+
+ def postinit(
+ self,
+ *,
+ value: NodeNG,
+ str: str, # pylint: disable=redefined-builtin
+ conversion: int = -1,
+ format_spec: JoinedStr | None = None,
+ ) -> None:
+ self.value = value
+ self.str = str
+ self.conversion = conversion
+ self.format_spec = format_spec
+
+ def get_children(self) -> Iterator[NodeNG]:
+ yield self.value
+ if self.format_spec:
+ yield self.format_spec
+
+
# constants ##############################################################
# The _proxied attribute of all container types (List, Tuple, etc.)
diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py
index dc8942b..a1255b9 100644
--- a/astroid/nodes/node_ng.py
+++ b/astroid/nodes/node_ng.py
@@ -14,7 +14,6 @@
Any,
ClassVar,
TypeVar,
- Union,
cast,
overload,
)
@@ -43,12 +42,14 @@
if TYPE_CHECKING:
from astroid.nodes import _base_nodes
+ FrameType = nodes.FunctionDef | nodes.Module | nodes.ClassDef | nodes.Lambda
+
# Types for 'NodeNG.nodes_of_class()'
_NodesT = TypeVar("_NodesT", bound="NodeNG")
_NodesT2 = TypeVar("_NodesT2", bound="NodeNG")
_NodesT3 = TypeVar("_NodesT3", bound="NodeNG")
-SkipKlassT = Union[None, type["NodeNG"], tuple[type["NodeNG"], ...]]
+SkipKlassT = None | type["NodeNG"] | tuple[type["NodeNG"], ...]
class NodeNG:
@@ -197,8 +198,11 @@
result = []
for field in self._other_fields + self._astroid_fields:
value = getattr(self, field, "Unknown")
- width = 80 - len(field) - alignment
- lines = pprint.pformat(value, indent=2, width=width).splitlines(True)
+ width = max(80 - len(field) - alignment, 1)
+ try:
+ lines = pprint.pformat(value, indent=2, width=width).splitlines(True)
+ except ValueError:
+ lines = [f"<{type(value).__name__}>"]
inner = [lines[0]]
for line in lines[1:]:
@@ -285,7 +289,7 @@
raise StatementMissing(target=self)
return self.parent.statement()
- def frame(self) -> nodes.FunctionDef | nodes.Module | nodes.ClassDef | nodes.Lambda:
+ def frame(self) -> FrameType:
"""The first parent frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
diff --git a/astroid/nodes/scoped_nodes/mixin.py b/astroid/nodes/scoped_nodes/mixin.py
index 8874c06..d10d317 100644
--- a/astroid/nodes/scoped_nodes/mixin.py
+++ b/astroid/nodes/scoped_nodes/mixin.py
@@ -6,7 +6,8 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, TypeVar, overload
+import sys
+from typing import TYPE_CHECKING, overload
from astroid.exceptions import ParentMissingError
from astroid.filter_statements import _filter_stmts
@@ -14,11 +15,13 @@
from astroid.nodes.scoped_nodes.utils import builtin_lookup
from astroid.typing import InferenceResult, SuccessfulInferenceResult
+if sys.version_info >= (3, 11):
+ from typing import Self
+else:
+ from typing_extensions import Self
if TYPE_CHECKING:
from astroid import nodes
-_T = TypeVar("_T")
-
class LocalsDictNodeNG(_base_nodes.LookupMixIn):
"""this class provides locals handling common to Module, FunctionDef
@@ -46,7 +49,7 @@
except ParentMissingError:
return self.name
- def scope(self: _T) -> _T:
+ def scope(self) -> Self:
"""The first parent node defining a new scope.
:returns: The first parent scope node.
diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py
index 020ea40..6345134 100644
--- a/astroid/nodes/scoped_nodes/scoped_nodes.py
+++ b/astroid/nodes/scoped_nodes/scoped_nodes.py
@@ -13,9 +13,10 @@
import io
import itertools
import os
+import sys
from collections.abc import Generator, Iterable, Iterator, Sequence
from functools import cached_property, lru_cache
-from typing import TYPE_CHECKING, Any, ClassVar, Literal, NoReturn, TypeVar
+from typing import TYPE_CHECKING, Any, ClassVar, Literal, NoReturn
from astroid import bases, protocols, util
from astroid.context import (
@@ -50,6 +51,11 @@
SuccessfulInferenceResult,
)
+if sys.version_info >= (3, 11):
+ from typing import Self
+else:
+ from typing_extensions import Self
+
if TYPE_CHECKING:
from astroid import nodes, objects
from astroid.nodes import Arguments, Const, NodeNG
@@ -62,8 +68,6 @@
{"classmethod", "staticmethod", "builtins.classmethod", "builtins.staticmethod"}
)
-_T = TypeVar("_T")
-
def _c3_merge(sequences, cls, context):
"""Merges MROs in *sequences* to a single MRO using the C3 algorithm.
@@ -587,7 +591,7 @@
def get_children(self):
yield from self.body
- def frame(self: _T, *, future: Literal[None, True] = None) -> _T:
+ def frame(self, *, future: Literal[None, True] = None) -> Self:
"""The node's frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
@@ -1030,7 +1034,7 @@
yield self.args
yield self.body
- def frame(self: _T, *, future: Literal[None, True] = None) -> _T:
+ def frame(self, *, future: Literal[None, True] = None) -> Self:
"""The node's frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
@@ -1223,7 +1227,7 @@
The property will return all the callables that are used for
decoration.
"""
- if not self.parent or not isinstance(frame := self.parent.frame(), ClassDef):
+ if not (self.parent and isinstance(frame := self.parent.frame(), ClassDef)):
return []
decorators: list[node_classes.Call] = []
@@ -1401,6 +1405,8 @@
:type: int
"""
+ if self.returns:
+ return self.returns.tolineno
return self.args.tolineno
def implicit_parameters(self) -> Literal[0, 1]:
@@ -1517,7 +1523,7 @@
) -> Generator[objects.Property | FunctionDef, None, InferenceErrorInfo]:
from astroid import objects # pylint: disable=import-outside-toplevel
- if not self.decorators or not bases._is_property(self):
+ if not (self.decorators and bases._is_property(self)):
yield self
return InferenceErrorInfo(node=self, context=context)
@@ -1618,6 +1624,16 @@
yield node_classes.Const(None)
return
+ # Builtin dunder methods have empty bodies, return Uninferable.
+ if (
+ len(self.body) == 0
+ and self.name.startswith("__")
+ and self.name.endswith("__")
+ and self.root().qname() == "builtins"
+ ):
+ yield util.Uninferable
+ return
+
raise InferenceError("The function does not have any return statements")
for returnnode in itertools.chain((first_return,), returns):
@@ -1675,7 +1691,7 @@
frame = self
return frame._scope_lookup(node, name, offset)
- def frame(self: _T, *, future: Literal[None, True] = None) -> _T:
+ def frame(self, *, future: Literal[None, True] = None) -> Self:
"""The node's frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
@@ -2346,8 +2362,10 @@
values += classnode.locals.get(name, [])
if name in self.special_attributes and class_context and not values:
- result = [self.special_attributes.lookup(name)]
- return result
+ special_attr = self.special_attributes.lookup(name)
+ if not isinstance(special_attr, node_classes.Unknown):
+ result = [special_attr]
+ return result
if class_context:
values += self._metaclass_lookup_attribute(name, context)
@@ -2725,9 +2743,10 @@
for elt in values:
try:
for inferred in elt.infer():
- if not isinstance(
- inferred, node_classes.Const
- ) or not isinstance(inferred.value, str):
+ if not (
+ isinstance(inferred, node_classes.Const)
+ and isinstance(inferred.value, str)
+ ):
continue
if not inferred.value:
continue
@@ -2822,11 +2841,41 @@
baseobj = baseobj._proxied
if not isinstance(baseobj, ClassDef):
continue
+ if baseobj is self:
+ # Circular base due to name rebinding (e.g. pdb.Pdb = CustomPdb
+ # where CustomPdb inherits from pdb.Pdb). Fall back to the
+ # first non-circular inferred value from the base expression.
+ baseobj = self._resolve_circular_base(stmt, context)
+ if baseobj is None:
+ continue
if not baseobj.hide:
yield baseobj
else:
yield from baseobj.bases
+ def _resolve_circular_base(
+ self,
+ stmt: nodes.NodeNG,
+ context: InferenceContext | None,
+ ) -> ClassDef | None:
+ """Resolve a circular base reference by finding the original class.
+
+ When a name is rebound to a subclass (e.g. ``pdb.Pdb = CustomPdb``),
+ ``_infer_last`` follows the rebinding and returns the subclass itself.
+ This method iterates through all inferred values to find the first
+ non-circular ClassDef.
+ """
+ inf_context = copy_context(context)
+ try:
+ for inferred in stmt.infer(context=inf_context):
+ if isinstance(inferred, bases.Instance):
+ inferred = inferred._proxied
+ if isinstance(inferred, ClassDef) and inferred is not self:
+ return inferred
+ except InferenceError:
+ pass
+ return None
+
def _compute_mro(self, context: InferenceContext | None = None):
if self.qname() == "builtins.object":
return [self]
@@ -2881,7 +2930,7 @@
)
return list(itertools.chain.from_iterable(children_assign_nodes))
- def frame(self: _T, *, future: Literal[None, True] = None) -> _T:
+ def frame(self, *, future: Literal[None, True] = None) -> Self:
"""The node's frame node.
A frame node is a :class:`Module`, :class:`FunctionDef`,
diff --git a/astroid/objects.py b/astroid/objects.py
index 9f638d4..bfc5ddc 100644
--- a/astroid/objects.py
+++ b/astroid/objects.py
@@ -13,9 +13,10 @@
from __future__ import annotations
+import sys
from collections.abc import Generator, Iterator
from functools import cached_property
-from typing import Any, Literal, NoReturn, TypeVar
+from typing import Any, Literal, NoReturn
from astroid import bases, util
from astroid.context import InferenceContext
@@ -30,7 +31,10 @@
from astroid.nodes import node_classes, scoped_nodes
from astroid.typing import InferenceResult, SuccessfulInferenceResult
-_T = TypeVar("_T")
+if sys.version_info >= (3, 11):
+ from typing import Self
+else:
+ from typing_extensions import Self
class FrozenSet(node_classes.BaseContainer):
@@ -190,7 +194,8 @@
# We can obtain different descriptors from a super depending
# on what we are accessing and where the super call is.
if inferred.type == "classmethod":
- yield bases.BoundMethod(inferred, cls)
+ # Pass original caller for classmethod too
+ yield bases.BoundMethod(inferred, cls, original_caller=self.type)
elif self._scope.type == "classmethod" and inferred.type == "method":
yield inferred
elif self._class_based or inferred.type == "staticmethod":
@@ -210,13 +215,17 @@
except InferenceError:
yield util.Uninferable
else:
- yield bases.BoundMethod(inferred, cls)
+ # Pass original caller (self.type) so infer_call_result can
+ # correctly resolve Self return types to the actual caller type
+ yield bases.BoundMethod(inferred, cls, original_caller=self.type)
# Only if we haven't found any explicit overwrites for the
# attribute we look it up in the special attributes
if not found and name in self.special_attributes:
- yield self.special_attributes.lookup(name)
- return
+ special_attr = self.special_attributes.lookup(name)
+ if not isinstance(special_attr, node_classes.Unknown):
+ yield special_attr
+ return
if not found:
raise AttributeInferenceError(target=self, attribute=name, context=context)
@@ -274,18 +283,15 @@
"""A class representing partial function obtained via functools.partial."""
def __init__(self, call, name=None, lineno=None, col_offset=None, parent=None):
- # TODO: Pass end_lineno, end_col_offset and parent as well
+ # TODO: Pass end_lineno, end_col_offset as well
super().__init__(
name,
lineno=lineno,
col_offset=col_offset,
- parent=scoped_nodes.SYNTHETIC_ROOT,
end_col_offset=0,
end_lineno=0,
+ parent=parent,
)
- # A typical FunctionDef automatically adds its name to the parent scope,
- # but a partial should not, so defer setting parent until after init
- self.parent = parent
self.filled_args = call.positional_arguments[1:]
self.filled_keywords = call.keyword_arguments
@@ -358,6 +364,6 @@
raise InferenceError("Properties are not callable")
def _infer(
- self: _T, context: InferenceContext | None = None, **kwargs: Any
- ) -> Generator[_T]:
+ self, context: InferenceContext | None = None, **kwargs: Any
+ ) -> Generator[Self]:
yield self
diff --git a/astroid/protocols.py b/astroid/protocols.py
index 8a837b8..6565688 100644
--- a/astroid/protocols.py
+++ b/astroid/protocols.py
@@ -15,6 +15,7 @@
from typing import TYPE_CHECKING, Any, TypeVar
from astroid import bases, decorators, nodes, util
+from astroid.builder import extract_node
from astroid.const import Context
from astroid.context import InferenceContext, copy_context
from astroid.exceptions import (
@@ -142,7 +143,7 @@
context: InferenceContext,
) -> _TupleListNodeT:
node = self.__class__(parent=opnode)
- if value <= 0 or not self.elts:
+ if not (value > 0 and self.elts):
node.elts = []
return node
if len(self.elts) * value > 1e8:
@@ -162,13 +163,13 @@
) -> Iterator[SuccessfulInferenceResult]:
for elt in elts:
if isinstance(elt, util.UninferableBase):
- yield nodes.Unknown()
+ yield node_classes.UNATTACHED_UNKNOWN
else:
for inferred in elt.infer(context):
if not isinstance(inferred, util.UninferableBase):
yield inferred
else:
- yield nodes.Unknown()
+ yield node_classes.UNATTACHED_UNKNOWN
@decorators.yes_if_nothing_inferred
@@ -527,11 +528,28 @@
) -> Any:
from astroid import objects # pylint: disable=import-outside-toplevel
- for assigned in node_classes.unpack_infer(self.type):
- if isinstance(assigned, nodes.ClassDef):
- assigned = objects.ExceptionInstance(assigned)
+ def _generate_assigned():
+ for assigned in node_classes.unpack_infer(self.type):
+ if isinstance(assigned, nodes.ClassDef):
+ assigned = objects.ExceptionInstance(assigned)
+ yield assigned
+
+ if isinstance(self.parent, node_classes.TryStar):
+ # except * handler has assigned ExceptionGroup with caught
+ # exceptions under exceptions attribute
+ # pylint: disable-next=stop-iteration-return
+ eg = next(node_classes.unpack_infer(extract_node("""
+from builtins import ExceptionGroup
+ExceptionGroup
+""")))
+ assigned = objects.ExceptionInstance(eg)
+ assigned.instance_attrs["exceptions"] = [
+ nodes.List.from_elements(_generate_assigned())
+ ]
yield assigned
+ else:
+ yield from _generate_assigned()
return {
"node": self,
"unknown": node,
@@ -809,7 +827,7 @@
if not isinstance(target, nodes.Tuple):
raise InferenceError(
- "Could not make sense of this, the target must be a tuple",
+ f"Could not make sense of this, the target must be a tuple, not {type(target)!r}",
context=context,
)
diff --git a/astroid/raw_building.py b/astroid/raw_building.py
index 150a40b..e7c5562 100644
--- a/astroid/raw_building.py
+++ b/astroid/raw_building.py
@@ -18,7 +18,7 @@
import warnings
from collections.abc import Iterable
from contextlib import redirect_stderr, redirect_stdout
-from typing import TYPE_CHECKING, Any, Union
+from typing import TYPE_CHECKING, Any
from astroid import bases, nodes
from astroid.const import _EMPTY_OBJECT_MARKER, IS_PYPY
@@ -30,14 +30,14 @@
logger = logging.getLogger(__name__)
-_FunctionTypes = Union[
- types.FunctionType,
- types.MethodType,
- types.BuiltinFunctionType,
- types.WrapperDescriptorType,
- types.MethodDescriptorType,
- types.ClassMethodDescriptorType,
-]
+_FunctionTypes = (
+ types.FunctionType
+ | types.MethodType
+ | types.BuiltinFunctionType
+ | types.WrapperDescriptorType
+ | types.MethodDescriptorType
+ | types.ClassMethodDescriptorType
+)
TYPE_NONE = type(None)
TYPE_NOTIMPLEMENTED = type(NotImplemented)
@@ -78,7 +78,11 @@
"""create a Const node and register it in the locals of the given
node with the specified name
"""
- if name not in node.special_attributes:
+ # Special case: __hash__ = None overrides ObjectModel for unhashable types.
+ # See https://docs.python.org/3/reference/datamodel.html#object.__hash__
+ if name == "__hash__" and value is None:
+ _attach_local_node(node, nodes.const_factory(value), name)
+ elif name not in node.special_attributes:
_attach_local_node(node, nodes.const_factory(value), name)
@@ -507,7 +511,11 @@
elif inspect.isdatadescriptor(member):
child = object_build_datadescriptor(node, member)
elif isinstance(member, tuple(node_classes.CONST_CLS)):
- if alias in node.special_attributes:
+ # Special case: __hash__ = None overrides ObjectModel for unhashable types.
+ # See https://docs.python.org/3/reference/datamodel.html#object.__hash__
+ if alias in node.special_attributes and not (
+ alias == "__hash__" and member is None
+ ):
continue
child = nodes.const_factory(member)
elif inspect.isroutine(member):
diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py
index b208754..f7d38a9 100644
--- a/astroid/rebuilder.py
+++ b/astroid/rebuilder.py
@@ -9,16 +9,17 @@
from __future__ import annotations
import ast
+import itertools
import sys
import token
-from collections.abc import Callable, Generator
+from collections.abc import Callable, Collection, Generator
from io import StringIO
from tokenize import TokenInfo, generate_tokens
-from typing import TYPE_CHECKING, Final, TypeVar, Union, cast, overload
+from typing import TYPE_CHECKING, Final, TypeVar, cast, overload
from astroid import nodes
from astroid._ast import ParserModule, get_parser_module, parse_function_type_comment
-from astroid.const import PY312_PLUS, Context
+from astroid.const import PY312_PLUS, PY313_PLUS, Context
from astroid.nodes.utils import Position
from astroid.typing import InferenceResult
@@ -27,14 +28,14 @@
T_Doc = TypeVar(
"T_Doc",
- "ast.Module",
- "ast.ClassDef",
- Union["ast.FunctionDef", "ast.AsyncFunctionDef"],
+ ast.Module,
+ ast.ClassDef,
+ ast.FunctionDef | ast.AsyncFunctionDef,
)
_FunctionT = TypeVar("_FunctionT", nodes.FunctionDef, nodes.AsyncFunctionDef)
_ForT = TypeVar("_ForT", nodes.For, nodes.AsyncFor)
_WithT = TypeVar("_WithT", nodes.With, nodes.AsyncWith)
- NodesWithDocsType = Union[nodes.Module, nodes.ClassDef, nodes.FunctionDef]
+ NodesWithDocsType = nodes.Module | nodes.ClassDef | nodes.FunctionDef
REDIRECT: Final[dict[str, str]] = {
@@ -61,7 +62,7 @@
self._manager = manager
self._data = data.split("\n") if data else None
self._global_names: list[dict[str, list[nodes.Global]]] = []
- self._import_from_nodes: list[nodes.ImportFrom] = []
+ self._import_from_nodes: list[tuple[nodes.ImportFrom, Collection[str]]] = []
self._delayed_assattr: list[nodes.AssignAttr] = []
self._visit_meths: dict[
type[ast.AST], Callable[[ast.AST, nodes.NodeNG], nodes.NodeNG]
@@ -72,7 +73,7 @@
else:
self._parser_module = parser_module
- def _get_doc(self, node: T_Doc) -> tuple[T_Doc, ast.Constant | ast.Str | None]:
+ def _get_doc(self, node: T_Doc) -> tuple[T_Doc, ast.Constant | None]:
"""Return the doc ast node."""
try:
if node.body and isinstance(node.body[0], ast.Expr):
@@ -413,60 +414,64 @@
self, node: ast.YieldFrom, parent: nodes.NodeNG
) -> nodes.YieldFrom: ...
- if sys.version_info >= (3, 10):
+ @overload
+ def visit(self, node: ast.Match, parent: nodes.NodeNG) -> nodes.Match: ...
- @overload
- def visit(self, node: ast.Match, parent: nodes.NodeNG) -> nodes.Match: ...
+ @overload
+ def visit(
+ self, node: ast.match_case, parent: nodes.NodeNG
+ ) -> nodes.MatchCase: ...
+
+ @overload
+ def visit(
+ self, node: ast.MatchValue, parent: nodes.NodeNG
+ ) -> nodes.MatchValue: ...
+
+ @overload
+ def visit(
+ self, node: ast.MatchSingleton, parent: nodes.NodeNG
+ ) -> nodes.MatchSingleton: ...
+
+ @overload
+ def visit(
+ self, node: ast.MatchSequence, parent: nodes.NodeNG
+ ) -> nodes.MatchSequence: ...
+
+ @overload
+ def visit(
+ self, node: ast.MatchMapping, parent: nodes.NodeNG
+ ) -> nodes.MatchMapping: ...
+
+ @overload
+ def visit(
+ self, node: ast.MatchClass, parent: nodes.NodeNG
+ ) -> nodes.MatchClass: ...
+
+ @overload
+ def visit(
+ self, node: ast.MatchStar, parent: nodes.NodeNG
+ ) -> nodes.MatchStar: ...
+
+ @overload
+ def visit(self, node: ast.MatchAs, parent: nodes.NodeNG) -> nodes.MatchAs: ...
+
+ @overload
+ def visit(self, node: ast.MatchOr, parent: nodes.NodeNG) -> nodes.MatchOr: ...
+
+ @overload
+ def visit(self, node: ast.pattern, parent: nodes.NodeNG) -> nodes.Pattern: ...
+
+ if sys.version_info >= (3, 14):
@overload
def visit(
- self, node: ast.match_case, parent: nodes.NodeNG
- ) -> nodes.MatchCase: ...
+ self, node: ast.TemplateStr, parent: nodes.NodeNG
+ ) -> nodes.TemplateStr: ...
@overload
def visit(
- self, node: ast.MatchValue, parent: nodes.NodeNG
- ) -> nodes.MatchValue: ...
-
- @overload
- def visit(
- self, node: ast.MatchSingleton, parent: nodes.NodeNG
- ) -> nodes.MatchSingleton: ...
-
- @overload
- def visit(
- self, node: ast.MatchSequence, parent: nodes.NodeNG
- ) -> nodes.MatchSequence: ...
-
- @overload
- def visit(
- self, node: ast.MatchMapping, parent: nodes.NodeNG
- ) -> nodes.MatchMapping: ...
-
- @overload
- def visit(
- self, node: ast.MatchClass, parent: nodes.NodeNG
- ) -> nodes.MatchClass: ...
-
- @overload
- def visit(
- self, node: ast.MatchStar, parent: nodes.NodeNG
- ) -> nodes.MatchStar: ...
-
- @overload
- def visit(
- self, node: ast.MatchAs, parent: nodes.NodeNG
- ) -> nodes.MatchAs: ...
-
- @overload
- def visit(
- self, node: ast.MatchOr, parent: nodes.NodeNG
- ) -> nodes.MatchOr: ...
-
- @overload
- def visit(
- self, node: ast.pattern, parent: nodes.NodeNG
- ) -> nodes.Pattern: ...
+ self, node: ast.Interpolation, parent: nodes.NodeNG
+ ) -> nodes.Interpolation: ...
@overload
def visit(self, node: ast.AST, parent: nodes.NodeNG) -> nodes.NodeNG: ...
@@ -585,6 +590,16 @@
type_comment_kwonlyargs=type_comment_kwonlyargs,
type_comment_posonlyargs=type_comment_posonlyargs,
)
+ if start_end_lineno_pairs := [
+ (arg.lineno, arg.end_lineno)
+ for arg in itertools.chain(
+ node.args, node.posonlyargs, node.kwonlyargs, [node.vararg, node.kwarg]
+ )
+ if arg
+ ]:
+ newnode.lineno = min(startend[0] for startend in start_end_lineno_pairs)
+ newnode.end_lineno = max(startend[1] for startend in start_end_lineno_pairs)
+
# save argument names in locals:
assert newnode.parent
if vararg:
@@ -1095,7 +1110,9 @@
parent=parent,
)
# store From names to add them to locals after building
- self._import_from_nodes.append(newnode)
+ self._import_from_nodes.append(
+ (newnode, self._global_names[-1].keys() if self._global_names else ())
+ )
return newnode
@overload
@@ -1296,8 +1313,11 @@
)
# save import names in parent's locals:
for name, asname in newnode.names:
- name = asname or name
- parent.set_local(name.split(".")[0], newnode)
+ name = (asname or name).split(".")[0]
+ if self._global_names and name in self._global_names[-1]:
+ parent.root().set_local(name, newnode)
+ else:
+ parent.set_local(name, newnode)
return newnode
def visit_joinedstr(
@@ -1437,7 +1457,7 @@
)
# XXX REMOVE me :
if context in (Context.Del, Context.Store): # 'Aug' ??
- newnode = cast(Union[nodes.AssignName, nodes.DelName], newnode)
+ newnode = cast((nodes.AssignName | nodes.DelName), newnode)
self._save_assignment(newnode)
return newnode
@@ -1479,7 +1499,12 @@
)
# Add AssignName node for 'node.name'
# https://bugs.python.org/issue43994
- newnode.postinit(name=self.visit_assignname(node, newnode, node.name))
+ newnode.postinit(
+ name=self.visit_assignname(node, newnode, node.name),
+ default_value=(
+ self.visit(node.default_value, newnode) if PY313_PLUS else None
+ ),
+ )
return newnode
def visit_pass(self, node: ast.Pass, parent: nodes.NodeNG) -> nodes.Pass:
@@ -1675,6 +1700,9 @@
newnode.postinit(
name=self.visit_assignname(node, newnode, node.name),
bound=self.visit(node.bound, newnode),
+ default_value=(
+ self.visit(node.default_value, newnode) if PY313_PLUS else None
+ ),
)
return newnode
@@ -1691,7 +1719,12 @@
)
# Add AssignName node for 'node.name'
# https://bugs.python.org/issue43994
- newnode.postinit(name=self.visit_assignname(node, newnode, node.name))
+ newnode.postinit(
+ name=self.visit_assignname(node, newnode, node.name),
+ default_value=(
+ self.visit(node.default_value, newnode) if PY313_PLUS else None
+ ),
+ )
return newnode
def visit_unaryop(self, node: ast.UnaryOp, parent: nodes.NodeNG) -> nodes.UnaryOp:
@@ -1790,10 +1823,160 @@
newnode.postinit(self.visit(node.value, newnode))
return newnode
- if sys.version_info >= (3, 10):
+ def visit_match(self, node: ast.Match, parent: nodes.NodeNG) -> nodes.Match:
+ newnode = nodes.Match(
+ lineno=node.lineno,
+ col_offset=node.col_offset,
+ end_lineno=node.end_lineno,
+ end_col_offset=node.end_col_offset,
+ parent=parent,
+ )
+ newnode.postinit(
+ subject=self.visit(node.subject, newnode),
+ cases=[self.visit(case, newnode) for case in node.cases],
+ )
+ return newnode
- def visit_match(self, node: ast.Match, parent: nodes.NodeNG) -> nodes.Match:
- newnode = nodes.Match(
+ def visit_matchcase(
+ self, node: ast.match_case, parent: nodes.NodeNG
+ ) -> nodes.MatchCase:
+ newnode = nodes.MatchCase(parent=parent)
+ newnode.postinit(
+ pattern=self.visit(node.pattern, newnode),
+ guard=self.visit(node.guard, newnode),
+ body=[self.visit(child, newnode) for child in node.body],
+ )
+ return newnode
+
+ def visit_matchvalue(
+ self, node: ast.MatchValue, parent: nodes.NodeNG
+ ) -> nodes.MatchValue:
+ newnode = nodes.MatchValue(
+ lineno=node.lineno,
+ col_offset=node.col_offset,
+ end_lineno=node.end_lineno,
+ end_col_offset=node.end_col_offset,
+ parent=parent,
+ )
+ newnode.postinit(value=self.visit(node.value, newnode))
+ return newnode
+
+ def visit_matchsingleton(
+ self, node: ast.MatchSingleton, parent: nodes.NodeNG
+ ) -> nodes.MatchSingleton:
+ return nodes.MatchSingleton(
+ value=node.value,
+ lineno=node.lineno,
+ col_offset=node.col_offset,
+ end_lineno=node.end_lineno,
+ end_col_offset=node.end_col_offset,
+ parent=parent,
+ )
+
+ def visit_matchsequence(
+ self, node: ast.MatchSequence, parent: nodes.NodeNG
+ ) -> nodes.MatchSequence:
+ newnode = nodes.MatchSequence(
+ lineno=node.lineno,
+ col_offset=node.col_offset,
+ end_lineno=node.end_lineno,
+ end_col_offset=node.end_col_offset,
+ parent=parent,
+ )
+ newnode.postinit(
+ patterns=[self.visit(pattern, newnode) for pattern in node.patterns]
+ )
+ return newnode
+
+ def visit_matchmapping(
+ self, node: ast.MatchMapping, parent: nodes.NodeNG
+ ) -> nodes.MatchMapping:
+ newnode = nodes.MatchMapping(
+ lineno=node.lineno,
+ col_offset=node.col_offset,
+ end_lineno=node.end_lineno,
+ end_col_offset=node.end_col_offset,
+ parent=parent,
+ )
+ # Add AssignName node for 'node.name'
+ # https://bugs.python.org/issue43994
+ newnode.postinit(
+ keys=[self.visit(child, newnode) for child in node.keys],
+ patterns=[self.visit(pattern, newnode) for pattern in node.patterns],
+ rest=self.visit_assignname(node, newnode, node.rest),
+ )
+ return newnode
+
+ def visit_matchclass(
+ self, node: ast.MatchClass, parent: nodes.NodeNG
+ ) -> nodes.MatchClass:
+ newnode = nodes.MatchClass(
+ lineno=node.lineno,
+ col_offset=node.col_offset,
+ end_lineno=node.end_lineno,
+ end_col_offset=node.end_col_offset,
+ parent=parent,
+ )
+ newnode.postinit(
+ cls=self.visit(node.cls, newnode),
+ patterns=[self.visit(pattern, newnode) for pattern in node.patterns],
+ kwd_attrs=node.kwd_attrs,
+ kwd_patterns=[
+ self.visit(pattern, newnode) for pattern in node.kwd_patterns
+ ],
+ )
+ return newnode
+
+ def visit_matchstar(
+ self, node: ast.MatchStar, parent: nodes.NodeNG
+ ) -> nodes.MatchStar:
+ newnode = nodes.MatchStar(
+ lineno=node.lineno,
+ col_offset=node.col_offset,
+ end_lineno=node.end_lineno,
+ end_col_offset=node.end_col_offset,
+ parent=parent,
+ )
+ # Add AssignName node for 'node.name'
+ # https://bugs.python.org/issue43994
+ newnode.postinit(name=self.visit_assignname(node, newnode, node.name))
+ return newnode
+
+ def visit_matchas(self, node: ast.MatchAs, parent: nodes.NodeNG) -> nodes.MatchAs:
+ newnode = nodes.MatchAs(
+ lineno=node.lineno,
+ col_offset=node.col_offset,
+ end_lineno=node.end_lineno,
+ end_col_offset=node.end_col_offset,
+ parent=parent,
+ )
+ # Add AssignName node for 'node.name'
+ # https://bugs.python.org/issue43994
+ newnode.postinit(
+ pattern=self.visit(node.pattern, newnode),
+ name=self.visit_assignname(node, newnode, node.name),
+ )
+ return newnode
+
+ def visit_matchor(self, node: ast.MatchOr, parent: nodes.NodeNG) -> nodes.MatchOr:
+ newnode = nodes.MatchOr(
+ lineno=node.lineno,
+ col_offset=node.col_offset,
+ end_lineno=node.end_lineno,
+ end_col_offset=node.end_col_offset,
+ parent=parent,
+ )
+ newnode.postinit(
+ patterns=[self.visit(pattern, newnode) for pattern in node.patterns]
+ )
+ return newnode
+
+ if sys.version_info >= (3, 14):
+
+ def visit_templatestr(
+ self, node: ast.TemplateStr, parent: nodes.NodeNG
+ ) -> nodes.TemplateStr:
+ newnode = nodes.TemplateStr(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
@@ -1801,51 +1984,14 @@
parent=parent,
)
newnode.postinit(
- subject=self.visit(node.subject, newnode),
- cases=[self.visit(case, newnode) for case in node.cases],
+ values=[self.visit(value, newnode) for value in node.values]
)
return newnode
- def visit_matchcase(
- self, node: ast.match_case, parent: nodes.NodeNG
- ) -> nodes.MatchCase:
- newnode = nodes.MatchCase(parent=parent)
- newnode.postinit(
- pattern=self.visit(node.pattern, newnode),
- guard=self.visit(node.guard, newnode),
- body=[self.visit(child, newnode) for child in node.body],
- )
- return newnode
-
- def visit_matchvalue(
- self, node: ast.MatchValue, parent: nodes.NodeNG
- ) -> nodes.MatchValue:
- newnode = nodes.MatchValue(
- lineno=node.lineno,
- col_offset=node.col_offset,
- end_lineno=node.end_lineno,
- end_col_offset=node.end_col_offset,
- parent=parent,
- )
- newnode.postinit(value=self.visit(node.value, newnode))
- return newnode
-
- def visit_matchsingleton(
- self, node: ast.MatchSingleton, parent: nodes.NodeNG
- ) -> nodes.MatchSingleton:
- return nodes.MatchSingleton(
- value=node.value,
- lineno=node.lineno,
- col_offset=node.col_offset,
- end_lineno=node.end_lineno,
- end_col_offset=node.end_col_offset,
- parent=parent,
- )
-
- def visit_matchsequence(
- self, node: ast.MatchSequence, parent: nodes.NodeNG
- ) -> nodes.MatchSequence:
- newnode = nodes.MatchSequence(
+ def visit_interpolation(
+ self, node: ast.Interpolation, parent: nodes.NodeNG
+ ) -> nodes.Interpolation:
+ newnode = nodes.Interpolation(
lineno=node.lineno,
col_offset=node.col_offset,
end_lineno=node.end_lineno,
@@ -1853,93 +1999,9 @@
parent=parent,
)
newnode.postinit(
- patterns=[self.visit(pattern, newnode) for pattern in node.patterns]
- )
- return newnode
-
- def visit_matchmapping(
- self, node: ast.MatchMapping, parent: nodes.NodeNG
- ) -> nodes.MatchMapping:
- newnode = nodes.MatchMapping(
- lineno=node.lineno,
- col_offset=node.col_offset,
- end_lineno=node.end_lineno,
- end_col_offset=node.end_col_offset,
- parent=parent,
- )
- # Add AssignName node for 'node.name'
- # https://bugs.python.org/issue43994
- newnode.postinit(
- keys=[self.visit(child, newnode) for child in node.keys],
- patterns=[self.visit(pattern, newnode) for pattern in node.patterns],
- rest=self.visit_assignname(node, newnode, node.rest),
- )
- return newnode
-
- def visit_matchclass(
- self, node: ast.MatchClass, parent: nodes.NodeNG
- ) -> nodes.MatchClass:
- newnode = nodes.MatchClass(
- lineno=node.lineno,
- col_offset=node.col_offset,
- end_lineno=node.end_lineno,
- end_col_offset=node.end_col_offset,
- parent=parent,
- )
- newnode.postinit(
- cls=self.visit(node.cls, newnode),
- patterns=[self.visit(pattern, newnode) for pattern in node.patterns],
- kwd_attrs=node.kwd_attrs,
- kwd_patterns=[
- self.visit(pattern, newnode) for pattern in node.kwd_patterns
- ],
- )
- return newnode
-
- def visit_matchstar(
- self, node: ast.MatchStar, parent: nodes.NodeNG
- ) -> nodes.MatchStar:
- newnode = nodes.MatchStar(
- lineno=node.lineno,
- col_offset=node.col_offset,
- end_lineno=node.end_lineno,
- end_col_offset=node.end_col_offset,
- parent=parent,
- )
- # Add AssignName node for 'node.name'
- # https://bugs.python.org/issue43994
- newnode.postinit(name=self.visit_assignname(node, newnode, node.name))
- return newnode
-
- def visit_matchas(
- self, node: ast.MatchAs, parent: nodes.NodeNG
- ) -> nodes.MatchAs:
- newnode = nodes.MatchAs(
- lineno=node.lineno,
- col_offset=node.col_offset,
- end_lineno=node.end_lineno,
- end_col_offset=node.end_col_offset,
- parent=parent,
- )
- # Add AssignName node for 'node.name'
- # https://bugs.python.org/issue43994
- newnode.postinit(
- pattern=self.visit(node.pattern, newnode),
- name=self.visit_assignname(node, newnode, node.name),
- )
- return newnode
-
- def visit_matchor(
- self, node: ast.MatchOr, parent: nodes.NodeNG
- ) -> nodes.MatchOr:
- newnode = nodes.MatchOr(
- lineno=node.lineno,
- col_offset=node.col_offset,
- end_lineno=node.end_lineno,
- end_col_offset=node.end_col_offset,
- parent=parent,
- )
- newnode.postinit(
- patterns=[self.visit(pattern, newnode) for pattern in node.patterns]
+ value=self.visit(node.value, parent),
+ str=node.str,
+ conversion=node.conversion,
+ format_spec=self.visit(node.format_spec, parent),
)
return newnode
diff --git a/astroid/transforms.py b/astroid/transforms.py
index 1831e74..d44ec3d 100644
--- a/astroid/transforms.py
+++ b/astroid/transforms.py
@@ -7,7 +7,7 @@
import warnings
from collections import defaultdict
from collections.abc import Callable
-from typing import TYPE_CHECKING, Optional, TypeVar, Union, cast, overload
+from typing import TYPE_CHECKING, TypeVar, Union, cast, overload
from astroid.context import _invalidate_cache
from astroid.typing import SuccessfulInferenceResult, TransformFn
@@ -18,18 +18,19 @@
_SuccessfulInferenceResultT = TypeVar(
"_SuccessfulInferenceResultT", bound=SuccessfulInferenceResult
)
- _Predicate = Optional[Callable[[_SuccessfulInferenceResultT], bool]]
+ _Predicate = Callable[[_SuccessfulInferenceResultT], bool] | None
+# pylint: disable-next=consider-alternative-union-syntax
_Vistables = Union[
"nodes.NodeNG", list["nodes.NodeNG"], tuple["nodes.NodeNG", ...], str, None
]
-_VisitReturns = Union[
- SuccessfulInferenceResult,
- list[SuccessfulInferenceResult],
- tuple[SuccessfulInferenceResult, ...],
- str,
- None,
-]
+_VisitReturns = (
+ SuccessfulInferenceResult
+ | list[SuccessfulInferenceResult]
+ | tuple[SuccessfulInferenceResult, ...]
+ | str
+ | None
+)
class TransformVisitor:
diff --git a/astroid/typing.py b/astroid/typing.py
index 77cc120..37ea434 100644
--- a/astroid/typing.py
+++ b/astroid/typing.py
@@ -47,6 +47,7 @@
_transform: transforms.TransformVisitor
+# pylint: disable=consider-alternative-union-syntax
InferenceResult = Union["nodes.NodeNG", "util.UninferableBase", "bases.Proxy"]
SuccessfulInferenceResult = Union["nodes.NodeNG", "bases.Proxy"]
_SuccessfulInferenceResultT = TypeVar(
diff --git a/doc/release.md b/doc/release.md
index 7dc7453..8e4ca83 100644
--- a/doc/release.md
+++ b/doc/release.md
@@ -30,10 +30,9 @@
git commit -am "Upgrade the version to 2.5.0-dev0 following 2.4.0 release"
```
-Check the commit and then push to a release branch
+Check the commit and then push to a release branch:
- Open a merge request with the two commits (no one can push directly on `main`)
-- Trigger the "release tests" workflow in GitHub Actions.
- After the merge, recover the merged commits on `main` and tag the first one (the
version should be `X.Y.Z`) as `vX.Y.Z` (For example: `v2.4.0`)
- Push the tag.
@@ -92,8 +91,8 @@
- Fix version conflicts properly, meaning preserve the version numbers of the form
`X.Y.0-devZ` (For example: `2.4.0-dev6`).
- Open a merge request against main. Ensure a merge commit is used, because pre-commit
- need the patch release tag to be in the main branch history to consider the patch
- release as the latest version and this won't be the case with rebase or squash. You
+ needs the patch release tag to be in the main branch history to consider the patch
+ release as the latest version, and this won't be the case with rebase or squash. You
can defend against trigger-happy future selves by enabling auto-merge with the merge
commit strategy.
- Wait for approval. Again, use a merge commit.
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 9048111..a759a74 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,3 +1,3 @@
-e .
-sphinx~=8.2
-furo==2024.8.6
+sphinx~=8.1
+furo==2025.12.19
diff --git a/pylintrc b/pylintrc
index 6421802..2957fff 100644
--- a/pylintrc
+++ b/pylintrc
@@ -42,10 +42,10 @@
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
-extension-pkg-whitelist=
+extension-pkg-whitelist=mypy
# Minimum supported python version
-py-version = 3.9.0
+py-version = 3.10.0
[REPORTS]
diff --git a/pyproject.toml b/pyproject.toml
index 4bf8398..04b1cc9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,17 +1,15 @@
[build-system]
build-backend = "setuptools.build_meta"
-
-requires = [ "setuptools>=75.2" ]
+requires = [ "setuptools>=77" ]
[project]
name = "astroid"
description = "An abstract syntax tree for Python with inference support."
readme = "README.rst"
keywords = [ "abstract syntax tree", "python", "static code analysis" ]
-license = "LGPL-2.1-or-later"
+license = "LGPL-2.1-OR-later"
license-files = [ "LICENSE", "CONTRIBUTORS.txt" ]
-
-requires-python = ">=3.9.0"
+requires-python = ">=3.10.0"
classifiers = [
"Development Status :: 6 - Mature",
"Environment :: Console",
@@ -19,11 +17,11 @@
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Libraries :: Python Modules",
@@ -31,7 +29,6 @@
"Topic :: Software Development :: Testing",
]
dynamic = [ "version" ]
-
dependencies = [
"typing-extensions>=4; python_version<'3.11'",
]
@@ -40,24 +37,17 @@
urls."Docs" = "https://pylint.readthedocs.io/projects/astroid/en/latest/"
urls."Source Code" = "https://github.com/pylint-dev/astroid"
-[tool.setuptools.package-dir]
-"" = "."
-
-[tool.setuptools.packages.find]
-include = [ "astroid*" ]
-
-[tool.setuptools.dynamic]
-version = { attr = "astroid.__pkginfo__.__version__" }
+[tool.setuptools]
+dynamic.version = { attr = "astroid.__pkginfo__.__version__" }
+package-dir."" = "."
+packages.find.include = [ "astroid*" ]
[tool.ruff]
-target-version = "py39"
-
+target-version = "py310"
# ruff is less lenient than pylint and does not make any exceptions
# (for docstrings, strings and comments in particular).
line-length = 110
-
extend-exclude = [ "tests/testdata/" ]
-
lint.select = [
"B", # bugbear
"E", # pycodestyle
@@ -83,17 +73,19 @@
]
lint.unfixable = [ "RUF001" ]
-[tool.pytest.ini_options]
-addopts = '-m "not acceptance"'
-python_files = [ "*test_*.py" ]
-testpaths = [ "tests" ]
-filterwarnings = "error"
+[tool.pyproject-fmt]
+max_supported_python = "3.14"
+
+[tool.pytest]
+ini_options.addopts = '-m "not acceptance"'
+ini_options.python_files = [ "*test_*.py" ]
+ini_options.testpaths = [ "tests" ]
+ini_options.filterwarnings = "error"
[tool.mypy]
-python_version = "3.9"
+python_version = "3.10"
files = [
"astroid/_ast.py",
- "astroid/_backport_stdlib_names.py",
"astroid/astroid_manager.py",
"astroid/brain/brain_crypt.py",
"astroid/brain/brain_ctypes.py",
@@ -131,7 +123,6 @@
"astroid/nodes/utils.py",
]
always_false = [
- "PY310_PLUS",
"PY311_PLUS",
"PY312_PLUS",
"PY313_PLUS",
@@ -151,7 +142,6 @@
"_io.*",
"gi.*",
"importlib.*",
- "nose.*",
"numpy.*",
"pytest",
"setuptools",
diff --git a/requirements_dev.txt b/requirements_dev.txt
index 77a6fd9..9f5ea56 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -3,5 +3,5 @@
# Tools used during development, prefer running these with pre-commit
black
pre-commit
-pylint>=3.2.7
+pylint>=4.0.0
ruff
diff --git a/requirements_full.txt b/requirements_full.txt
index 5b6f600..12efafb 100644
--- a/requirements_full.txt
+++ b/requirements_full.txt
@@ -3,12 +3,12 @@
# Packages used to run additional tests
attrs
-nose
numpy>=1.17.0,<3; python_version<"3.12"
python-dateutil
PyQt6
regex
-setuptools; python_version>="3.12"
+# Pinned as 82.0.0 removes `pkg_resources` which we require for a test.
+setuptools<82; python_version>="3.12"
six
urllib3>1,<2
typing_extensions>=4.4.0
diff --git a/requirements_minimal.txt b/requirements_minimal.txt
index db9260e..c1457cf 100644
--- a/requirements_minimal.txt
+++ b/requirements_minimal.txt
@@ -3,7 +3,7 @@
tbump~=6.11
# Tools used to run tests
-coverage~=7.7
+coverage~=7.13
pytest
-pytest-cov~=6.0
-mypy
+pytest-cov~=7.0
+mypy; platform_python_implementation!="PyPy"
diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json
index 7bf909b..56a97d5 100644
--- a/script/.contributors_aliases.json
+++ b/script/.contributors_aliases.json
@@ -29,6 +29,10 @@
],
"name": "Artem Yurchenko"
},
+ "53538590+zenlyj@users.noreply.github.com": {
+ "mails": ["53538590+zenlyj@users.noreply.github.com", "zenlyj97@gmail.com"],
+ "name": "Zen Lee"
+ },
"55152140+jayaddison@users.noreply.github.com": {
"mails": ["55152140+jayaddison@users.noreply.github.com", "jay@jp-hosting.net"],
"name": "James Addison"
@@ -72,7 +76,9 @@
"mails": [
"66853113+pre-commit-ci[bot]@users.noreply.github.com",
"49699333+dependabot[bot]@users.noreply.github.com",
- "41898282+github-actions[bot]@users.noreply.github.com"
+ "41898282+github-actions[bot]@users.noreply.github.com",
+ "212256041+pylint-backport[bot]@users.noreply.github.com",
+ "212256041+pylint-backport-bot[bot]@users.noreply.github.com"
],
"name": "bot"
},
@@ -81,6 +87,10 @@
"name": "Bryce Guinta",
"team": "Maintainers"
},
+ "c.ringstrom@gmail.com": {
+ "mails": ["c.ringstrom@gmail.com"],
+ "name": "Charlie Ringström"
+ },
"calen.pennington@gmail.com": {
"mails": ["cale@edx.org", "calen.pennington@gmail.com"],
"name": "Calen Pennington"
@@ -99,13 +109,20 @@
"mails": ["david@dropbox.com", "github@euresti.com"],
"name": "David Euresti"
},
+ "grayjk@gmail.com": {
+ "mails": ["grayjk@gmail.com"],
+ "name": "grayjk"
+ },
"guillaume.peillex@gmail.com": {
"mails": ["guillaume.peillex@gmail.com"],
"name": "Hippo91",
"team": "Maintainers"
},
"hugovk@users.noreply.github.com": {
- "mails": ["hugovk@users.noreply.github.com"],
+ "mails": [
+ "hugovk@users.noreply.github.com",
+ "1324225+hugovk@users.noreply.github.com"
+ ],
"name": "Hugo van Kemenade"
},
"jacob@bogdanov.dev": {
@@ -133,6 +150,10 @@
"mails": ["mcorcherojim@bloomberg.net", "mariocj89@gmail.com"],
"name": "Mario Corchero"
},
+ "matusvalo@users.noreply.github.com": {
+ "mails": ["matusvalo@users.noreply.github.com"],
+ "name": "Matus Valo"
+ },
"me@the-compiler.org": {
"mails": ["me@the-compiler.org"],
"name": "Florian Bruhin",
diff --git a/script/bump_changelog.py b/script/bump_changelog.py
index a08a1ae..a00ae95 100644
--- a/script/bump_changelog.py
+++ b/script/bump_changelog.py
@@ -5,6 +5,7 @@
"""
This script permits to upgrade the changelog in astroid or pylint when releasing a version.
"""
+
# pylint: disable=logging-fstring-interpolation
from __future__ import annotations
@@ -34,7 +35,7 @@
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
logging.debug(f"Launching bump_changelog with args: {args}")
- if any(s in args.version for s in ("dev", "a", "b")):
+ if any(s in args.version for s in ("dev", "a", "b", "rc")):
return
with open(DEFAULT_CHANGELOG_PATH, encoding="utf-8") as f:
content = f.read()
diff --git a/tbump.toml b/tbump.toml
index eb79c03..4fdd86b 100644
--- a/tbump.toml
+++ b/tbump.toml
@@ -1,7 +1,7 @@
github_url = "https://github.com/pylint-dev/astroid"
[version]
-current = "4.0.0dev1"
+current = "4.2.0-dev0"
regex = '''
^(?P<major>0|[1-9]\d*)
\.
diff --git a/tests/brain/numpy/test_core_einsumfunc.py b/tests/brain/numpy/test_core_einsumfunc.py
index c2760ca..95f3e23 100644
--- a/tests/brain/numpy/test_core_einsumfunc.py
+++ b/tests/brain/numpy/test_core_einsumfunc.py
@@ -17,13 +17,11 @@
def _inferred_numpy_func_call(func_name: str, *func_args: str) -> nodes.FunctionDef:
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy as np
func = np.{func_name:s}
func({','.join(func_args):s})
- """
- )
+ """)
return node.infer()
@@ -43,12 +41,10 @@
@pytest.mark.skipif(not HAS_NUMPY, reason="This test requires the numpy library.")
def test_function_parameters() -> None:
- instance = builder.extract_node(
- """
+ instance = builder.extract_node("""
import numpy
numpy.einsum #@
- """
- )
+ """)
actual_args = instance.inferred()[0].args
assert actual_args.vararg == "operands"
diff --git a/tests/brain/numpy/test_core_fromnumeric.py b/tests/brain/numpy/test_core_fromnumeric.py
index 1d78257..9442ab6 100644
--- a/tests/brain/numpy/test_core_fromnumeric.py
+++ b/tests/brain/numpy/test_core_fromnumeric.py
@@ -21,13 +21,11 @@
numpy_functions = (("sum", "[1, 2]"),)
def _inferred_numpy_func_call(self, func_name, *func_args):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy as np
func = np.{func_name:s}
func({','.join(func_args):s})
- """
- )
+ """)
return node.infer()
def test_numpy_function_calls_inferred_as_ndarray(self):
diff --git a/tests/brain/numpy/test_core_function_base.py b/tests/brain/numpy/test_core_function_base.py
index 5d42946..f2ef004 100644
--- a/tests/brain/numpy/test_core_function_base.py
+++ b/tests/brain/numpy/test_core_function_base.py
@@ -25,13 +25,11 @@
)
def _inferred_numpy_func_call(self, func_name, *func_args):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy as np
func = np.{func_name:s}
func({','.join(func_args):s})
- """
- )
+ """)
return node.infer()
def test_numpy_function_calls_inferred_as_ndarray(self):
diff --git a/tests/brain/numpy/test_core_multiarray.py b/tests/brain/numpy/test_core_multiarray.py
index e7ccde3..f06f06a 100644
--- a/tests/brain/numpy/test_core_multiarray.py
+++ b/tests/brain/numpy/test_core_multiarray.py
@@ -60,23 +60,19 @@
)
def _inferred_numpy_func_call(self, func_name, *func_args):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy as np
func = np.{func_name:s}
func({','.join(func_args):s})
- """
- )
+ """)
return node.infer()
def _inferred_numpy_no_alias_func_call(self, func_name, *func_args):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy
func = numpy.{func_name:s}
func({','.join(func_args):s})
- """
- )
+ """)
return node.infer()
def test_numpy_function_calls_inferred_as_ndarray(self):
diff --git a/tests/brain/numpy/test_core_numeric.py b/tests/brain/numpy/test_core_numeric.py
index 8970ca3..a11becc 100644
--- a/tests/brain/numpy/test_core_numeric.py
+++ b/tests/brain/numpy/test_core_numeric.py
@@ -30,13 +30,11 @@
)
def _inferred_numpy_func_call(self, func_name, *func_args):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy as np
func = np.{func_name:s}
func({','.join(func_args):s})
- """
- )
+ """)
return node.infer()
def test_numpy_function_calls_inferred_as_ndarray(self):
@@ -68,11 +66,9 @@
],
)
def test_function_parameters(method: str, expected_args: list[str]) -> None:
- instance = builder.extract_node(
- f"""
+ instance = builder.extract_node(f"""
import numpy
numpy.{method} #@
- """
- )
+ """)
actual_args = instance.inferred()[0].args.args
assert [arg.name for arg in actual_args] == expected_args
diff --git a/tests/brain/numpy/test_core_numerictypes.py b/tests/brain/numpy/test_core_numerictypes.py
index 819cc7d..51c2cf2 100644
--- a/tests/brain/numpy/test_core_numerictypes.py
+++ b/tests/brain/numpy/test_core_numerictypes.py
@@ -80,11 +80,9 @@
]
def _inferred_numpy_attribute(self, attrib):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy.core.numerictypes as tested_module
- missing_type = tested_module.{attrib:s}"""
- )
+ missing_type = tested_module.{attrib:s}""")
return next(node.value.infer())
def test_numpy_core_types(self):
@@ -311,14 +309,12 @@
pylint-dev/pylint#3332
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import numpy as np
import datetime
test_array = np.datetime64(1, 'us')
test_array.astype(datetime.datetime)
- """
- )
+ """)
licit_array_types = ".ndarray"
inferred_values = list(node.infer())
self.assertTrue(
diff --git a/tests/brain/numpy/test_core_umath.py b/tests/brain/numpy/test_core_umath.py
index a288d58..83fd731 100644
--- a/tests/brain/numpy/test_core_umath.py
+++ b/tests/brain/numpy/test_core_umath.py
@@ -101,12 +101,10 @@
constants = ("e", "euler_gamma")
def _inferred_numpy_attribute(self, func_name):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy.core.umath as tested_module
func = tested_module.{func_name:s}
- func"""
- )
+ func""")
return next(node.infer())
def test_numpy_core_umath_constants(self):
@@ -183,13 +181,11 @@
self.assertEqual(default_args_values, exact_kwargs_default_values)
def _inferred_numpy_func_call(self, func_name, *func_args):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy as np
func = np.{func_name:s}
func()
- """
- )
+ """)
return node.infer()
def test_numpy_core_umath_functions_return_type(self):
diff --git a/tests/brain/numpy/test_ndarray.py b/tests/brain/numpy/test_ndarray.py
index 9ccadf5..8678d54 100644
--- a/tests/brain/numpy/test_ndarray.py
+++ b/tests/brain/numpy/test_ndarray.py
@@ -105,23 +105,19 @@
)
def _inferred_ndarray_method_call(self, func_name):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy as np
test_array = np.ndarray((2, 2))
test_array.{func_name:s}()
- """
- )
+ """)
return node.infer()
def _inferred_ndarray_attribute(self, attr_name):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy as np
test_array = np.ndarray((2, 2))
test_array.{attr_name:s}
- """
- )
+ """)
return node.infer()
def test_numpy_function_calls_inferred_as_ndarray(self):
diff --git a/tests/brain/numpy/test_random_mtrand.py b/tests/brain/numpy/test_random_mtrand.py
index 7a3c324..91a962d 100644
--- a/tests/brain/numpy/test_random_mtrand.py
+++ b/tests/brain/numpy/test_random_mtrand.py
@@ -74,12 +74,10 @@
}
def _inferred_numpy_attribute(self, func_name):
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import numpy.random.mtrand as tested_module
func = tested_module.{func_name:s}
- func"""
- )
+ func""")
return next(node.infer())
def test_numpy_random_mtrand_functions(self):
diff --git a/tests/brain/test_argparse.py b/tests/brain/test_argparse.py
index 20d96e2..75b5e3f 100644
--- a/tests/brain/test_argparse.py
+++ b/tests/brain/test_argparse.py
@@ -8,13 +8,11 @@
class TestBrainArgparse:
@staticmethod
def test_infer_namespace() -> None:
- func = extract_node(
- """
+ func = extract_node("""
import argparse
def make_namespace(): #@
return argparse.Namespace(debug=True)
- """
- )
+ """)
assert isinstance(func, nodes.FunctionDef)
inferred = next(func.infer_call_result(func))
assert isinstance(inferred, bases.Instance)
diff --git a/tests/brain/test_attr.py b/tests/brain/test_attr.py
index ef48873..63d5a74 100644
--- a/tests/brain/test_attr.py
+++ b/tests/brain/test_attr.py
@@ -20,8 +20,7 @@
@unittest.skipUnless(HAS_ATTR, "These tests require the attr library")
class AttrsTest(unittest.TestCase):
def test_attr_transform(self) -> None:
- module = astroid.parse(
- """
+ module = astroid.parse("""
import attr
from attr import attrs, attrib, field
@@ -80,12 +79,11 @@
d: int = attr.Factory(lambda: 3)
m = Eggs(d=1)
- """
- )
+ """)
for name in ("f", "g", "h", "i", "j", "k", "l", "m"):
should_be_unknown = next(module.getattr(name)[0].infer()).getattr("d")[0]
- self.assertIsInstance(should_be_unknown, astroid.Unknown)
+ self.assertIsInstance(should_be_unknown, nodes.Unknown)
def test_attrs_transform(self) -> None:
"""Test brain for decorators of the 'attrs' package.
@@ -93,8 +91,7 @@
Package added support for 'attrs' alongside 'attr' in v21.3.0.
See: https://github.com/python-attrs/attrs/releases/tag/21.3.0
"""
- module = astroid.parse(
- """
+ module = astroid.parse("""
import attrs
from attrs import field, mutable, frozen, define
from attrs import mutable as my_mutable
@@ -153,12 +150,11 @@
@frozen
class Legs:
d = attrs.field(default=attrs.Factory(dict))
- """
- )
+ """)
for name in ("f", "g", "h", "i", "j", "k", "l"):
should_be_unknown = next(module.getattr(name)[0].infer()).getattr("d")[0]
- self.assertIsInstance(should_be_unknown, astroid.Unknown, name)
+ self.assertIsInstance(should_be_unknown, nodes.Unknown, name)
def test_special_attributes(self) -> None:
"""Make sure special attrs attributes exist"""
@@ -200,7 +196,7 @@
Foo()
"""
should_be_unknown = next(astroid.extract_node(code).infer()).getattr("bar")[0]
- self.assertIsInstance(should_be_unknown, astroid.Unknown)
+ self.assertIsInstance(should_be_unknown, nodes.Unknown)
def test_attr_with_only_annotation_fails(self) -> None:
code = """
@@ -228,4 +224,60 @@
should_be_unknown = next(astroid.extract_node(code).infer()).getattr(
attr_name
)[0]
- self.assertIsInstance(should_be_unknown, astroid.Unknown)
+ self.assertIsInstance(should_be_unknown, nodes.Unknown)
+
+ def test_attrs_with_class_var_annotation(self) -> None:
+ cases = {
+ "with-subscript": """
+ import attrs
+ from typing import ClassVar
+
+ @attrs.define
+ class Foo:
+ bar: ClassVar[int] = 1
+ Foo()
+ """,
+ "no-subscript": """
+ import attrs
+ from typing import ClassVar
+
+ @attrs.define
+ class Foo:
+ bar: ClassVar = 1
+ Foo()
+ """,
+ }
+
+ for name, code in cases.items():
+ with self.subTest(case=name):
+ instance = next(astroid.extract_node(code).infer())
+ self.assertIsInstance(instance.getattr("bar")[0], nodes.AssignName)
+ self.assertNotIn("bar", instance.instance_attrs)
+
+ def test_attrs_without_class_var_annotation(self) -> None:
+ cases = {
+ "wrong-name": """
+ import attrs
+ from typing import Final
+
+ @attrs.define
+ class Foo:
+ bar: Final[int] = 1
+ Foo()
+ """,
+ "classvar-not-outermost": """
+ import attrs
+ from typing import ClassVar
+
+ @attrs.define
+ class Foo:
+ bar: list[ClassVar[int]] = []
+ Foo()
+ """,
+ }
+
+ for name, code in cases.items():
+ with self.subTest(case=name):
+ instance = next(astroid.extract_node(code).infer())
+ self.assertIsInstance(instance.getattr("bar")[0], nodes.Unknown)
+ self.assertIn("bar", instance.instance_attrs)
diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py
index 610dcd0..0b60ac2 100644
--- a/tests/brain/test_brain.py
+++ b/tests/brain/test_brain.py
@@ -21,24 +21,20 @@
InferenceError,
UseInferenceDefault,
)
-from astroid.nodes.node_classes import Const
-from astroid.nodes.scoped_nodes import ClassDef
-def assertEqualMro(klass: ClassDef, expected_mro: list[str]) -> None:
+def assertEqualMro(klass: nodes.ClassDef, expected_mro: list[str]) -> None:
"""Check mro names."""
assert [member.qname() for member in klass.mro()] == expected_mro
class CollectionsDequeTests(unittest.TestCase):
def _inferred_queue_instance(self) -> Instance:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import collections
q = collections.deque([])
q
- """
- )
+ """)
return next(node.infer())
def test_deque(self) -> None:
@@ -58,13 +54,11 @@
class OrderedDictTest(unittest.TestCase):
def _inferred_ordered_dict_instance(self) -> Instance:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import collections
d = collections.OrderedDict()
d
- """
- )
+ """)
return next(node.infer())
def test_ordered_dict_py34method(self) -> None:
@@ -74,14 +68,12 @@
class DefaultDictTest(unittest.TestCase):
def test_1(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import defaultdict
X = defaultdict(int)
X[0]
- """
- )
+ """)
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred)
@@ -113,12 +105,10 @@
)
def test_sys_streams(self):
for name in ("stdout", "stderr", "stdin"):
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
import sys
sys.{name}
- """
- )
+ """)
inferred = next(node.infer())
buffer_attr = next(inferred.igetattr("buffer"))
self.assertIsInstance(buffer_attr, astroid.Instance)
@@ -128,23 +118,20 @@
self.assertEqual(raw.name, "FileIO")
-@test_utils.require_version("3.9")
class TypeBrain(unittest.TestCase):
def test_type_subscript(self):
"""
Check that type object has the __class_getitem__ method
when it is used as a subscript
"""
- src = builder.extract_node(
- """
+ src = builder.extract_node("""
a: type[int] = int
- """
- )
+ """)
val_inf = src.annotation.value.inferred()[0]
- self.assertIsInstance(val_inf, astroid.ClassDef)
+ self.assertIsInstance(val_inf, nodes.ClassDef)
self.assertEqual(val_inf.name, "type")
meth_inf = val_inf.getattr("__class_getitem__")[0]
- self.assertIsInstance(meth_inf, astroid.FunctionDef)
+ self.assertIsInstance(meth_inf, nodes.FunctionDef)
def test_invalid_type_subscript(self):
"""
@@ -152,13 +139,11 @@
from type does not have __class_getitem__ method even
when it is used as a subscript
"""
- src = builder.extract_node(
- """
+ src = builder.extract_node("""
a: str[int] = "abc"
- """
- )
+ """)
val_inf = src.annotation.value.inferred()[0]
- self.assertIsInstance(val_inf, astroid.ClassDef)
+ self.assertIsInstance(val_inf, nodes.ClassDef)
self.assertEqual(val_inf.name, "str")
with self.assertRaises(AttributeInferenceError):
# pylint: disable=expression-not-assigned
@@ -182,7 +167,7 @@
if PY312_PLUS and node.name == "ByteString":
# .metaclass() finds the first metaclass in the mro(),
# which, from 3.12, is _DeprecateByteStringMeta (unhelpful)
- # until ByteString is removed in 3.14.
+ # until ByteString is removed in 3.17.
# Jump over the first two ByteString classes in the mro().
check_metaclass_is_abc(node.mro()[2])
else:
@@ -213,20 +198,16 @@
Test that unsubscriptable types are detected
Hashable is not subscriptable even with python39
"""
- wrong_node = builder.extract_node(
- """
+ wrong_node = builder.extract_node("""
import collections.abc
collections.abc.Hashable[int]
- """
- )
+ """)
with self.assertRaises(InferenceError):
next(wrong_node.infer())
- right_node = builder.extract_node(
- """
+ right_node = builder.extract_node("""
import collections.abc
collections.abc.Hashable
- """
- )
+ """)
inferred = next(right_node.infer())
check_metaclass_is_abc(inferred)
assertEqualMro(
@@ -241,12 +222,10 @@
def test_collections_object_subscriptable(self):
"""Starting with python39 some object of collections module are subscriptable. Test one of them"""
- right_node = builder.extract_node(
- """
+ right_node = builder.extract_node("""
import collections.abc
collections.abc.MutableSet[int]
- """
- )
+ """)
inferred = next(right_node.infer())
check_metaclass_is_abc(inferred)
assertEqualMro(
@@ -265,52 +244,13 @@
inferred.getattr("__class_getitem__")[0], nodes.FunctionDef
)
- @test_utils.require_version(maxver="3.9")
- def test_collections_object_not_yet_subscriptable(self):
- """
- Test that unsubscriptable types are detected as such.
- Until python39 MutableSet of the collections module is not subscriptable.
- """
- wrong_node = builder.extract_node(
- """
- import collections.abc
- collections.abc.MutableSet[int]
- """
- )
- with self.assertRaises(InferenceError):
- next(wrong_node.infer())
- right_node = builder.extract_node(
- """
- import collections.abc
- collections.abc.MutableSet
- """
- )
- inferred = next(right_node.infer())
- check_metaclass_is_abc(inferred)
- assertEqualMro(
- inferred,
- [
- "_collections_abc.MutableSet",
- "_collections_abc.Set",
- "_collections_abc.Collection",
- "_collections_abc.Sized",
- "_collections_abc.Iterable",
- "_collections_abc.Container",
- "builtins.object",
- ],
- )
- with self.assertRaises(AttributeInferenceError):
- inferred.getattr("__class_getitem__")
-
def test_collections_object_subscriptable_2(self):
"""Starting with python39 Iterator in the collection.abc module is subscriptable"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import collections.abc
class Derived(collections.abc.Iterator[int]):
pass
- """
- )
+ """)
inferred = next(node.infer())
check_metaclass_is_abc(inferred)
assertEqualMro(
@@ -323,27 +263,13 @@
],
)
- @test_utils.require_version(maxver="3.9")
- def test_collections_object_not_yet_subscriptable_2(self):
- """Before python39 Iterator in the collection.abc module is not subscriptable"""
- node = builder.extract_node(
- """
- import collections.abc
- collections.abc.Iterator[int]
- """
- )
- with self.assertRaises(InferenceError):
- next(node.infer())
-
def test_collections_object_subscriptable_3(self):
"""With Python 3.9 the ByteString class of the collections module is subscriptable
(but not the same class from typing module)"""
- right_node = builder.extract_node(
- """
+ right_node = builder.extract_node("""
import collections.abc
collections.abc.ByteString[int]
- """
- )
+ """)
inferred = next(right_node.infer())
check_metaclass_is_abc(inferred)
self.assertIsInstance(
@@ -352,13 +278,11 @@
def test_collections_object_subscriptable_4(self):
"""Multiple inheritance with subscriptable collection class"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import collections.abc
class Derived(collections.abc.Hashable, collections.abc.Iterator[int]):
pass
- """
- )
+ """)
inferred = next(node.infer())
assertEqualMro(
inferred,
@@ -371,17 +295,23 @@
],
)
+ def test_statistics_quantiles_from_import(self):
+ node = builder.extract_node("""
+ from statistics import quantiles
+ quantiles([1, 2, 3, 4, 5, 6, 7, 8, 9], n=4)
+ """)
+ inferred = next(node.infer())
+ self.assertIs(inferred, util.Uninferable)
+
class TypingBrain(unittest.TestCase):
def test_namedtuple_base(self) -> None:
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
from typing import NamedTuple
class X(NamedTuple("X", [("a", int), ("b", str), ("c", bytes)])):
pass
- """
- )
+ """)
self.assertEqual(
[anc.name for anc in klass.ancestors()], ["X", "tuple", "object"]
)
@@ -389,8 +319,7 @@
self.assertFalse(anc.parent is None)
def test_namedtuple_can_correctly_access_methods(self) -> None:
- klass, called = builder.extract_node(
- """
+ klass, called = builder.extract_node("""
from typing import NamedTuple
class X(NamedTuple): #@
@@ -401,87 +330,73 @@
def as_integer(self):
return 2 + 3
X().as_integer() #@
- """
- )
+ """)
self.assertEqual(len(klass.getattr("as_string")), 1)
inferred = next(called.infer())
- self.assertIsInstance(inferred, astroid.Const)
+ self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 5)
def test_namedtuple_inference(self) -> None:
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
from typing import NamedTuple
class X(NamedTuple("X", [("a", int), ("b", str), ("c", bytes)])):
pass
- """
- )
+ """)
base = next(base for base in klass.ancestors() if base.name == "X")
self.assertSetEqual({"a", "b", "c"}, set(base.instance_attrs))
def test_namedtuple_inference_nonliteral(self) -> None:
# Note: NamedTuples in mypy only work with literals.
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
from typing import NamedTuple
name = "X"
fields = [("a", int), ("b", str), ("c", bytes)]
NamedTuple(name, fields)
- """
- )
+ """)
inferred = next(klass.infer())
self.assertIsInstance(inferred, astroid.Instance)
self.assertEqual(inferred.qname(), "typing.NamedTuple")
def test_namedtuple_instance_attrs(self) -> None:
- result = builder.extract_node(
- """
+ result = builder.extract_node("""
from typing import NamedTuple
NamedTuple("A", [("a", int), ("b", str), ("c", bytes)])(1, 2, 3) #@
- """
- )
+ """)
inferred = next(result.infer())
for name, attr in inferred.instance_attrs.items():
self.assertEqual(attr[0].attrname, name)
def test_namedtuple_simple(self) -> None:
- result = builder.extract_node(
- """
+ result = builder.extract_node("""
from typing import NamedTuple
NamedTuple("A", [("a", int), ("b", str), ("c", bytes)])
- """
- )
+ """)
inferred = next(result.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
self.assertSetEqual({"a", "b", "c"}, set(inferred.instance_attrs))
def test_namedtuple_few_args(self) -> None:
- result = builder.extract_node(
- """
+ result = builder.extract_node("""
from typing import NamedTuple
NamedTuple("A")
- """
- )
+ """)
inferred = next(result.infer())
self.assertIsInstance(inferred, astroid.Instance)
self.assertEqual(inferred.qname(), "typing.NamedTuple")
def test_namedtuple_few_fields(self) -> None:
- result = builder.extract_node(
- """
+ result = builder.extract_node("""
from typing import NamedTuple
NamedTuple("A", [("a",), ("b", str), ("c", bytes)])
- """
- )
+ """)
inferred = next(result.infer())
self.assertIsInstance(inferred, astroid.Instance)
self.assertEqual(inferred.qname(), "typing.NamedTuple")
def test_namedtuple_class_form(self) -> None:
- result = builder.extract_node(
- """
+ result = builder.extract_node("""
from typing import NamedTuple
class Example(NamedTuple):
@@ -489,23 +404,20 @@
mything: int
Example(mything=1)
- """
- )
+ """)
inferred = next(result.infer())
self.assertIsInstance(inferred, astroid.Instance)
class_attr = inferred.getattr("CLASS_ATTR")[0]
- self.assertIsInstance(class_attr, astroid.AssignName)
+ self.assertIsInstance(class_attr, nodes.AssignName)
const = next(class_attr.infer())
self.assertEqual(const.value, "class_attr")
def test_namedtuple_inferred_as_class(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import NamedTuple
NamedTuple
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.ClassDef)
assert inferred.name == "NamedTuple"
@@ -515,38 +427,31 @@
https://github.com/pylint-dev/pylint/issues/4383
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
if True:
def NamedTuple():
pass
NamedTuple
- """
- )
+ """)
next(node.infer())
def test_namedtuple_uninferable_member(self) -> None:
- call = builder.extract_node(
- """
+ call = builder.extract_node("""
from typing import namedtuple
- namedtuple('uninf', {x: x for x in range(0)}) #@"""
- )
+ namedtuple('uninf', {x: x for x in range(0)}) #@""")
with pytest.raises(UseInferenceDefault):
_get_namedtuple_fields(call)
- call = builder.extract_node(
- """
+ call = builder.extract_node("""
from typing import namedtuple
uninferable = {x: x for x in range(0)}
namedtuple('uninferable', uninferable) #@
- """
- )
+ """)
with pytest.raises(UseInferenceDefault):
_get_namedtuple_fields(call)
def test_typing_types(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
from typing import TypeVar, Iterable, Tuple, NewType, Dict, Union
TypeVar('MyTypeVar', int, float, complex) #@
Iterable[Tuple[MyTypeVar, MyTypeVar]] #@
@@ -554,28 +459,24 @@
NewType('UserId', str) #@
Dict[str, str] #@
Union[int, str] #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.ClassDef, node.as_string())
def test_typing_type_without_tip(self):
"""Regression test for https://github.com/pylint-dev/pylint/issues/5770"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import NewType
def make_new_type(t):
new_type = NewType(f'IntRange_{t}', t) #@
- """
- )
+ """)
with self.assertRaises(UseInferenceDefault):
astroid.brain.brain_typing.infer_typing_typevar_or_newtype(node.value)
def test_namedtuple_nested_class(self):
- result = builder.extract_node(
- """
+ result = builder.extract_node("""
from typing import NamedTuple
class Example(NamedTuple):
@@ -583,36 +484,31 @@
bar = "bar"
Example
- """
- )
+ """)
inferred = next(result.infer())
- self.assertIsInstance(inferred, astroid.ClassDef)
+ self.assertIsInstance(inferred, nodes.ClassDef)
class_def_attr = inferred.getattr("Foo")[0]
- self.assertIsInstance(class_def_attr, astroid.ClassDef)
+ self.assertIsInstance(class_def_attr, nodes.ClassDef)
attr_def = class_def_attr.getattr("bar")[0]
attr = next(attr_def.infer())
self.assertEqual(attr.value, "bar")
def test_tuple_type(self):
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import Tuple
Tuple[int, int]
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.ClassDef)
assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
assert inferred.qname() == "typing.Tuple"
def test_callable_type(self):
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import Callable, Any
Callable[..., Any]
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.ClassDef)
assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
@@ -620,13 +516,11 @@
def test_typing_generic_subscriptable(self):
"""Test typing.Generic is subscriptable with __class_getitem__ (added in PY37)"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import Generic, TypeVar
T = TypeVar('T')
Generic[T]
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.ClassDef)
assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
@@ -634,12 +528,10 @@
@test_utils.require_version(minver="3.12")
def test_typing_generic_subscriptable_pep695(self):
"""Test class using type parameters is subscriptable with __class_getitem__ (added in PY312)"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class Foo[T]: ...
class Bar[T](Foo[T]): ...
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.ClassDef)
assert inferred.name == "Bar"
@@ -651,12 +543,10 @@
def test_typing_annotated_subscriptable(self):
"""typing.Annotated is subscriptable with __class_getitem__ below 3.13."""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import typing
typing.Annotated[str, "data"]
- """
- )
+ """)
inferred = next(node.infer())
if PY313_PLUS:
assert isinstance(inferred, nodes.FunctionDef)
@@ -668,16 +558,14 @@
def test_typing_generic_slots(self):
"""Test slots for Generic subclass."""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import Generic, TypeVar
T = TypeVar('T')
class A(Generic[T]):
__slots__ = ['value']
def __init__(self, value):
self.value = value
- """
- )
+ """)
inferred = next(node.infer())
slots = inferred.slots()
assert len(slots) == 1
@@ -685,40 +573,32 @@
assert slots[0].value == "value"
def test_typing_no_duplicates(self):
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import List
List[int]
- """
- )
+ """)
assert len(node.inferred()) == 1
def test_typing_no_duplicates_2(self):
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import Optional, Tuple
Tuple[Optional[int], ...]
- """
- )
+ """)
assert len(node.inferred()) == 1
- @test_utils.require_version(minver="3.10")
def test_typing_param_spec(self):
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import ParamSpec
P = ParamSpec("P")
- """
- )
+ """)
inferred = next(node.targets[0].infer())
assert next(inferred.igetattr("args")) is not None
assert next(inferred.igetattr("kwargs")) is not None
def test_collections_generic_alias_slots(self):
"""Test slots for a class which is a subclass of a generic alias type."""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import collections
import typing
Type = typing.TypeVar('Type')
@@ -726,8 +606,7 @@
__slots__ = ('_value',)
def __init__(self, value: collections.abc.AsyncIterator[Type]):
self._value = value
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.ClassDef)
slots = inferred.slots()
@@ -736,38 +615,32 @@
assert slots[0].value == "_value"
def test_has_dunder_args(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
from typing import Union
NumericTypes = Union[int, float]
NumericTypes.__args__ #@
- """
- )
+ """)
inferred = next(ast_node.infer())
assert isinstance(inferred, nodes.Tuple)
def test_typing_namedtuple_dont_crash_on_no_fields(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import NamedTuple
Bar = NamedTuple("bar", [])
Bar()
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, astroid.Instance)
def test_typed_dict(self):
- code = builder.extract_node(
- """
+ code = builder.extract_node("""
from typing import TypedDict
class CustomTD(TypedDict): #@
var: int
CustomTD(var=1) #@
- """
- )
+ """)
inferred_base = next(code[0].bases[0].infer())
assert isinstance(inferred_base, nodes.ClassDef)
assert inferred_base.qname() == "typing.TypedDict"
@@ -788,8 +661,7 @@
correctly inferred.
typing_alias function is introduced with python37
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import TypeVar, MutableSet
T = TypeVar("T")
@@ -797,8 +669,7 @@
class Derived1(MutableSet[T]):
pass
- """
- )
+ """)
inferred = next(node.infer())
assertEqualMro(
inferred,
@@ -822,13 +693,11 @@
typing_alias function is introduced with python37.
OrderedDict in the typing module appears only with python 3.7.2
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import typing
class Derived2(typing.OrderedDict[int, str]):
pass
- """
- )
+ """)
inferred = next(node.infer())
assertEqualMro(
inferred,
@@ -843,20 +712,16 @@
def test_typing_object_not_subscriptable(self):
"""Hashable is not subscriptable"""
- wrong_node = builder.extract_node(
- """
+ wrong_node = builder.extract_node("""
import typing
typing.Hashable[int]
- """
- )
+ """)
with self.assertRaises(InferenceError):
next(wrong_node.infer())
- right_node = builder.extract_node(
- """
+ right_node = builder.extract_node("""
import typing
typing.Hashable
- """
- )
+ """)
inferred = next(right_node.infer())
assertEqualMro(
inferred,
@@ -871,12 +736,10 @@
def test_typing_object_subscriptable(self):
"""Test that MutableSet is subscriptable"""
- right_node = builder.extract_node(
- """
+ right_node = builder.extract_node("""
import typing
typing.MutableSet[int]
- """
- )
+ """)
inferred = next(right_node.infer())
assertEqualMro(
inferred,
@@ -897,13 +760,11 @@
def test_typing_object_subscriptable_2(self):
"""Multiple inheritance with subscriptable typing alias"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import typing
class Derived(typing.Hashable, typing.Iterator[int]):
pass
- """
- )
+ """)
inferred = next(node.infer())
assertEqualMro(
inferred,
@@ -919,14 +780,12 @@
)
def test_typing_object_notsubscriptable_3(self):
- """Until python39 ByteString class of the typing module is not
+ """The ByteString class of the typing module is not
subscriptable (whereas it is in the collections' module)"""
- right_node = builder.extract_node(
- """
+ right_node = builder.extract_node("""
import typing
typing.ByteString
- """
- )
+ """)
inferred = next(right_node.infer())
check_metaclass_is_abc(inferred)
with self.assertRaises(AttributeInferenceError):
@@ -950,51 +809,44 @@
@staticmethod
def test_typing_type_subscriptable():
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import Type
Type[int]
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.ClassDef)
assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef)
assert inferred.qname() == "typing.Type"
def test_typing_cast(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import cast
class A:
pass
b = 42
cast(A, b)
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value == 42
def test_typing_cast_attribute(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import typing
class A:
pass
b = 42
typing.cast(A, b)
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value == 42
def test_typing_cast_multiple_inference_calls(self) -> None:
"""Inference of an outer function should not store the result for cast."""
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
from typing import TypeVar, cast
T = TypeVar("T")
def ident(var: T) -> T:
@@ -1002,8 +854,7 @@
ident(2) #@
ident("Hello") #@
- """
- )
+ """)
i0 = next(ast_nodes[0].infer())
assert isinstance(i0, nodes.Const)
assert i0.value == 2
@@ -1021,73 +872,20 @@
self.assertIn(name, re_ast)
self.assertEqual(next(re_ast[name].infer()).value, getattr(re, name))
- @test_utils.require_version(maxver="3.9")
- def test_re_pattern_unsubscriptable(self):
- """
- re.Pattern and re.Match are unsubscriptable until PY39.
- """
- right_node1 = builder.extract_node(
- """
- import re
- re.Pattern
- """
- )
- inferred1 = next(right_node1.infer())
- assert isinstance(inferred1, nodes.ClassDef)
- with self.assertRaises(AttributeInferenceError):
- assert isinstance(
- inferred1.getattr("__class_getitem__")[0], nodes.FunctionDef
- )
-
- right_node2 = builder.extract_node(
- """
- import re
- re.Pattern
- """
- )
- inferred2 = next(right_node2.infer())
- assert isinstance(inferred2, nodes.ClassDef)
- with self.assertRaises(AttributeInferenceError):
- assert isinstance(
- inferred2.getattr("__class_getitem__")[0], nodes.FunctionDef
- )
-
- wrong_node1 = builder.extract_node(
- """
- import re
- re.Pattern[int]
- """
- )
- with self.assertRaises(InferenceError):
- next(wrong_node1.infer())
-
- wrong_node2 = builder.extract_node(
- """
- import re
- re.Match[int]
- """
- )
- with self.assertRaises(InferenceError):
- next(wrong_node2.infer())
-
def test_re_pattern_subscriptable(self):
"""Test re.Pattern and re.Match are subscriptable in PY39+"""
- node1 = builder.extract_node(
- """
+ node1 = builder.extract_node("""
import re
re.Pattern[str]
- """
- )
+ """)
inferred1 = next(node1.infer())
assert isinstance(inferred1, nodes.ClassDef)
assert isinstance(inferred1.getattr("__class_getitem__")[0], nodes.FunctionDef)
- node2 = builder.extract_node(
- """
+ node2 = builder.extract_node("""
import re
re.Match[str]
- """
- )
+ """)
inferred2 = next(node2.infer())
assert isinstance(inferred2, nodes.ClassDef)
assert isinstance(inferred2.getattr("__class_getitem__")[0], nodes.FunctionDef)
@@ -1095,80 +893,112 @@
class BrainNamedtupleAnnAssignTest(unittest.TestCase):
def test_no_crash_on_ann_assign_in_namedtuple(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from enum import Enum
from typing import Optional
class A(Enum):
B: str = 'B'
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
class BrainUUIDTest(unittest.TestCase):
def test_uuid_has_int_member(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import uuid
u = uuid.UUID('{12345678-1234-5678-1234-567812345678}')
u.int
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Const)
class RandomSampleTest(unittest.TestCase):
def test_inferred_successfully(self) -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
import random
random.sample([1, 2], 2) #@
- """
- )
+ """)
inferred = next(node.infer())
- self.assertIsInstance(inferred, astroid.List)
+ self.assertIsInstance(inferred, nodes.List)
elems = sorted(elem.value for elem in inferred.elts)
self.assertEqual(elems, [1, 2])
def test_arguments_inferred_successfully(self) -> None:
"""Test inference of `random.sample` when both arguments are of type `nodes.Call`."""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
import random
def sequence():
return [1, 2]
random.sample(sequence(), len([1,2])) #@
- """
- )
+ """)
# Check that arguments are of type `nodes.Call`.
sequence, length = node.args
- self.assertIsInstance(sequence, astroid.Call)
- self.assertIsInstance(length, astroid.Call)
+ self.assertIsInstance(sequence, nodes.Call)
+ self.assertIsInstance(length, nodes.Call)
# Check the inference of `random.sample` call.
inferred = next(node.infer())
- self.assertIsInstance(inferred, astroid.List)
+ self.assertIsInstance(inferred, nodes.List)
elems = sorted(elem.value for elem in inferred.elts)
self.assertEqual(elems, [1, 2])
def test_no_crash_on_evaluatedobject(self) -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
from random import sample
class A: pass
- sample(list({1: A()}.values()), 1)"""
- )
+ sample(list({1: A()}.values()), 1)""")
inferred = next(node.infer())
- assert isinstance(inferred, astroid.List)
+ assert isinstance(inferred, nodes.List)
assert len(inferred.elts) == 1
assert isinstance(inferred.elts[0], nodes.Call)
+ def test_no_crash_on_uninferable_element(self) -> None:
+ """Test that random.sample does not crash when elements are Uninferable.
+
+ Regression test for https://github.com/pylint-dev/astroid/issues/2518
+ """
+ node = astroid.extract_node("""
+ import random
+ random.sample(1*[b], 1) #@
+ """)
+ inferred = next(node.infer())
+ assert inferred is astroid.Uninferable
+
+ def test_no_crash_on_classdef_clone(self) -> None:
+ """Test that random.sample does not crash when cloning ClassDef nodes.
+
+ Regression test for https://github.com/pylint-dev/astroid/issues/2923
+ """
+ node = astroid.extract_node("""
+ import random
+ random.sample([dict] * 2, 1) #@
+ """)
+ inferred = next(node.infer())
+ assert isinstance(inferred, nodes.List)
+ assert len(inferred.elts) == 1
+ assert isinstance(inferred.elts[0], nodes.ClassDef)
+ assert inferred.elts[0].name == "dict"
+
+ def test_no_crash_on_functiondef_clone(self) -> None:
+ """Test that random.sample does not crash when cloning FunctionDef nodes.
+
+ Regression test for https://github.com/pylint-dev/astroid/issues/2923
+ """
+ node = astroid.extract_node("""
+ import random
+ random.sample([len] * 2, 1) #@
+ """)
+ inferred = next(node.infer())
+ assert isinstance(inferred, nodes.List)
+ assert len(inferred.elts) == 1
+ assert isinstance(inferred.elts[0], nodes.FunctionDef)
+ assert inferred.elts[0].name == "len"
+
class SubprocessTest(unittest.TestCase):
"""Test subprocess brain"""
@@ -1177,13 +1007,11 @@
"""Make sure the args attribute exists for Popen
Test for https://github.com/pylint-dev/pylint/issues/1860"""
- name = astroid.extract_node(
- """
+ name = astroid.extract_node("""
import subprocess
p = subprocess.Popen(['ls'])
p #@
- """
- )
+ """)
[inst] = name.inferred()
self.assertIsInstance(next(inst.igetattr("args")), nodes.List)
@@ -1196,10 +1024,9 @@
node = astroid.extract_node(code)
inferred = next(node.infer())
# Can be either str or bytes
- assert isinstance(inferred, astroid.Const)
+ assert isinstance(inferred, nodes.Const)
assert isinstance(inferred.value, (str, bytes))
- @test_utils.require_version("3.9")
def test_popen_does_not_have_class_getitem(self):
code = """import subprocess; subprocess.Popen"""
node = astroid.extract_node(code)
@@ -1227,54 +1054,34 @@
assert _get_result("isinstance('a', int)") == "False"
def test_isinstance_object_true(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Bar(object):
pass
isinstance(Bar(), object)
- """
- )
- == "True"
- )
+ """) == "True"
def test_isinstance_object_true3(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Bar(object):
pass
isinstance(Bar(), Bar)
- """
- )
- == "True"
- )
+ """) == "True"
def test_isinstance_class_false(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Foo(object):
pass
class Bar(object):
pass
isinstance(Bar(), Foo)
- """
- )
- == "False"
- )
+ """) == "False"
def test_isinstance_type_false(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Bar(object):
pass
isinstance(Bar(), type)
- """
- )
- == "False"
- )
+ """) == "False"
def test_isinstance_str_true(self) -> None:
"""Make sure isinstance can check builtin str types"""
@@ -1288,40 +1095,25 @@
assert _get_result("isinstance(1, (str, int))") == "True"
def test_isinstance_type_false2(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
isinstance(1, type)
- """
- )
- == "False"
- )
+ """) == "False"
def test_isinstance_object_true2(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Bar(type):
pass
mainbar = Bar("Bar", tuple(), {})
isinstance(mainbar, object)
- """
- )
- == "True"
- )
+ """) == "True"
def test_isinstance_type_true(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Bar(type):
pass
mainbar = Bar("Bar", tuple(), {})
isinstance(mainbar, type)
- """
- )
- == "True"
- )
+ """) == "True"
def test_isinstance_edge_case(self) -> None:
"""isinstance allows bad type short-circuting"""
@@ -1366,70 +1158,45 @@
assert _get_result("issubclass(str, int)") == "False"
def test_issubclass_object_true(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Bar(object):
pass
issubclass(Bar, object)
- """
- )
- == "True"
- )
+ """) == "True"
def test_issubclass_same_user_defined_class(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Bar(object):
pass
issubclass(Bar, Bar)
- """
- )
- == "True"
- )
+ """) == "True"
def test_issubclass_different_user_defined_classes(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Foo(object):
pass
class Bar(object):
pass
issubclass(Bar, Foo)
- """
- )
- == "False"
- )
+ """) == "False"
def test_issubclass_type_false(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Bar(object):
pass
issubclass(Bar, type)
- """
- )
- == "False"
- )
+ """) == "False"
def test_isinstance_tuple_argument(self) -> None:
"""obj just has to be a subclass of ANY class/type on the right"""
assert _get_result("issubclass(int, (str, int))") == "True"
def test_isinstance_object_true2(self) -> None:
- assert (
- _get_result(
- """
+ assert _get_result("""
class Bar(type):
pass
issubclass(Bar, object)
- """
- )
- == "True"
- )
+ """) == "True"
def test_issubclass_short_circuit(self) -> None:
"""issubclasss allows bad type short-circuting"""
@@ -1452,7 +1219,7 @@
_get_result_node("issubclass(int, int, str)")
-def _get_result_node(code: str) -> Const:
+def _get_result_node(code: str) -> nodes.Const:
node = next(astroid.extract_node(code).infer())
return node
@@ -1464,72 +1231,59 @@
class TestLenBuiltinInference:
def test_len_list(self) -> None:
# Uses .elts
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
len(['a','b','c'])
- """
- )
+ """)
node = next(node.infer())
assert node.as_string() == "3"
assert isinstance(node, nodes.Const)
def test_len_tuple(self) -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
len(('a','b','c'))
- """
- )
+ """)
node = next(node.infer())
assert node.as_string() == "3"
def test_len_var(self) -> None:
# Make sure argument is inferred
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
a = [1,2,'a','b','c']
len(a)
- """
- )
+ """)
node = next(node.infer())
assert node.as_string() == "5"
def test_len_dict(self) -> None:
# Uses .items
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
a = {'a': 1, 'b': 2}
len(a)
- """
- )
+ """)
node = next(node.infer())
assert node.as_string() == "2"
def test_len_set(self) -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
len({'a'})
- """
- )
+ """)
inferred_node = next(node.infer())
assert inferred_node.as_string() == "1"
def test_len_object(self) -> None:
"""Test len with objects that implement the len protocol"""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
class A:
def __len__(self):
return 57
len(A())
- """
- )
+ """)
inferred_node = next(node.infer())
assert inferred_node.as_string() == "57"
def test_len_class_with_metaclass(self) -> None:
"""Make sure proper len method is located"""
- cls_node, inst_node = astroid.extract_node(
- """
+ cls_node, inst_node = astroid.extract_node("""
class F2(type):
def __new__(cls, name, bases, attrs):
return super().__new__(cls, name, bases, {})
@@ -1540,59 +1294,48 @@
return 4
len(F) #@
len(F()) #@
- """
- )
+ """)
assert next(cls_node.infer()).as_string() == "57"
assert next(inst_node.infer()).as_string() == "4"
def test_len_object_failure(self) -> None:
"""If taking the length of a class, do not use an instance method"""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
class F:
def __len__(self):
return 57
len(F)
- """
- )
+ """)
with pytest.raises(InferenceError):
next(node.infer())
def test_len_string(self) -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
len("uwu")
- """
- )
+ """)
assert next(node.infer()).as_string() == "3"
def test_len_generator_failure(self) -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
def gen():
yield 'a'
yield 'b'
len(gen())
- """
- )
+ """)
with pytest.raises(InferenceError):
next(node.infer())
def test_len_failure_missing_variable(self) -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
len(a)
- """
- )
+ """)
with pytest.raises(InferenceError):
next(node.infer())
def test_len_bytes(self) -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
len(b'uwu')
- """
- )
+ """)
assert next(node.infer()).as_string() == "3"
def test_int_subclass_result(self) -> None:
@@ -1602,8 +1345,7 @@
int subclass (5) but still returns a proper integer as we
fake the result of the `len()` call.
"""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
class IntSubclass(int):
pass
@@ -1611,21 +1353,18 @@
def __len__(self):
return IntSubclass(5)
len(F())
- """
- )
+ """)
assert next(node.infer()).as_string() == "0"
@pytest.mark.xfail(reason="Can't use list special astroid fields")
def test_int_subclass_argument(self):
"""I am unable to access the length of an object which
subclasses list"""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
class ListSubclass(list):
pass
len(ListSubclass([1,2,3,4,4]))
- """
- )
+ """)
assert next(node.infer()).as_string() == "5"
def test_len_builtin_inference_attribute_error_str(self) -> None:
@@ -1664,47 +1403,39 @@
def test_infer_str() -> None:
- ast_nodes = astroid.extract_node(
- """
+ ast_nodes = astroid.extract_node("""
str(s) #@
str('a') #@
str(some_object()) #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
- assert isinstance(inferred, astroid.Const)
+ assert isinstance(inferred, nodes.Const)
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
str(s='') #@
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, astroid.Instance)
assert inferred.qname() == "builtins.str"
def test_infer_int() -> None:
- ast_nodes = astroid.extract_node(
- """
+ ast_nodes = astroid.extract_node("""
int(0) #@
int('1') #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
- assert isinstance(inferred, astroid.Const)
+ assert isinstance(inferred, nodes.Const)
- ast_nodes = astroid.extract_node(
- """
+ ast_nodes = astroid.extract_node("""
int(s='') #@
int('2.5') #@
int('something else') #@
int(unknown) #@
int(b'a') #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
assert isinstance(inferred, astroid.Instance)
@@ -1712,87 +1443,75 @@
def test_infer_dict_from_keys() -> None:
- bad_nodes = astroid.extract_node(
- """
+ bad_nodes = astroid.extract_node("""
dict.fromkeys() #@
dict.fromkeys(1, 2, 3) #@
dict.fromkeys(a=1) #@
- """
- )
+ """)
for node in bad_nodes:
with pytest.raises(InferenceError):
if isinstance(next(node.infer()), util.UninferableBase):
raise InferenceError
# Test uninferable values
- good_nodes = astroid.extract_node(
- """
+ good_nodes = astroid.extract_node("""
from unknown import Unknown
dict.fromkeys(some_value) #@
dict.fromkeys(some_other_value) #@
dict.fromkeys([Unknown(), Unknown()]) #@
dict.fromkeys([Unknown(), Unknown()]) #@
- """
- )
+ """)
for node in good_nodes:
inferred = next(node.infer())
- assert isinstance(inferred, astroid.Dict)
+ assert isinstance(inferred, nodes.Dict)
assert inferred.items == []
# Test inferable values
# from a dictionary's keys
- from_dict = astroid.extract_node(
- """
+ from_dict = astroid.extract_node("""
dict.fromkeys({'a':2, 'b': 3, 'c': 3}) #@
- """
- )
+ """)
inferred = next(from_dict.infer())
- assert isinstance(inferred, astroid.Dict)
+ assert isinstance(inferred, nodes.Dict)
itered = inferred.itered()
- assert all(isinstance(elem, astroid.Const) for elem in itered)
+ assert all(isinstance(elem, nodes.Const) for elem in itered)
actual_values = [elem.value for elem in itered]
assert sorted(actual_values) == ["a", "b", "c"]
# from a string
- from_string = astroid.extract_node(
- """
+ from_string = astroid.extract_node("""
dict.fromkeys('abc')
- """
- )
+ """)
inferred = next(from_string.infer())
- assert isinstance(inferred, astroid.Dict)
+ assert isinstance(inferred, nodes.Dict)
itered = inferred.itered()
- assert all(isinstance(elem, astroid.Const) for elem in itered)
+ assert all(isinstance(elem, nodes.Const) for elem in itered)
actual_values = [elem.value for elem in itered]
assert sorted(actual_values) == ["a", "b", "c"]
# from bytes
- from_bytes = astroid.extract_node(
- """
+ from_bytes = astroid.extract_node("""
dict.fromkeys(b'abc')
- """
- )
+ """)
inferred = next(from_bytes.infer())
- assert isinstance(inferred, astroid.Dict)
+ assert isinstance(inferred, nodes.Dict)
itered = inferred.itered()
- assert all(isinstance(elem, astroid.Const) for elem in itered)
+ assert all(isinstance(elem, nodes.Const) for elem in itered)
actual_values = [elem.value for elem in itered]
assert sorted(actual_values) == [97, 98, 99]
# From list/set/tuple
- from_others = astroid.extract_node(
- """
+ from_others = astroid.extract_node("""
dict.fromkeys(('a', 'b', 'c')) #@
dict.fromkeys(['a', 'b', 'c']) #@
dict.fromkeys({'a', 'b', 'c'}) #@
- """
- )
+ """)
for node in from_others:
inferred = next(node.infer())
- assert isinstance(inferred, astroid.Dict)
+ assert isinstance(inferred, nodes.Dict)
itered = inferred.itered()
- assert all(isinstance(elem, astroid.Const) for elem in itered)
+ assert all(isinstance(elem, nodes.Const) for elem in itered)
actual_values = [elem.value for elem in itered]
assert sorted(actual_values) == ["a", "b", "c"]
@@ -1800,15 +1519,13 @@
class TestFunctoolsPartial:
@staticmethod
def test_infer_partial() -> None:
- ast_node = astroid.extract_node(
- """
+ ast_node = astroid.extract_node("""
from functools import partial
def test(a, b):
'''Docstring'''
return a + b
partial(test, 1)(3) #@
- """
- )
+ """)
assert isinstance(ast_node.func, nodes.Call)
inferred = ast_node.func.inferred()
assert len(inferred) == 1
@@ -1821,8 +1538,7 @@
assert partial.col_offset == 0
def test_invalid_functools_partial_calls(self) -> None:
- ast_nodes = astroid.extract_node(
- """
+ ast_nodes = astroid.extract_node("""
from functools import partial
from unknown import Unknown
@@ -1836,19 +1552,17 @@
partial(Unknown, a=1) #@
partial(2, a=1) #@
partial(test, unknown=1) #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
- assert isinstance(inferred, (astroid.FunctionDef, astroid.Instance))
+ assert isinstance(inferred, (nodes.FunctionDef, astroid.Instance))
assert inferred.qname() in {
"functools.partial",
"functools.partial.newfunc",
}
def test_inferred_partial_function_calls(self) -> None:
- ast_nodes = astroid.extract_node(
- """
+ ast_nodes = astroid.extract_node("""
from functools import partial
def test(a, b):
return a + b
@@ -1866,18 +1580,16 @@
test(1, 2) #@
partial(other_test, 1, 2)(c=3) #@
partial(test, b=4)(a=3) #@
- """
- )
+ """)
expected_values = [4, 7, 7, 3, 12, 16, 32, 36, 3, 9, 7]
for node, expected_value in zip(ast_nodes, expected_values):
inferred = next(node.infer())
- assert isinstance(inferred, astroid.Const)
+ assert isinstance(inferred, nodes.Const)
assert inferred.value == expected_value
def test_partial_assignment(self) -> None:
"""Make sure partials are not assigned to original scope."""
- ast_nodes = astroid.extract_node(
- """
+ ast_nodes = astroid.extract_node("""
from functools import partial
def test(a, b): #@
return a + b
@@ -1886,8 +1598,7 @@
def test3_scope(a):
test3 = partial(test, a)
test3 #@
- """
- )
+ """)
func1, func2, func3 = ast_nodes
assert func1.parent.scope() == func2.parent.scope()
assert func1.parent.scope() != func3.parent.scope()
@@ -1898,16 +1609,14 @@
def test_partial_does_not_affect_scope(self) -> None:
"""Make sure partials are not automatically assigned."""
- ast_nodes = astroid.extract_node(
- """
+ ast_nodes = astroid.extract_node("""
from functools import partial
def test(a, b):
return a + b
def scope():
test2 = partial(test, 1)
test2 #@
- """
- )
+ """)
test2 = next(ast_nodes.infer())
mod_scope = test2.root()
scope = test2.parent.scope()
@@ -1916,8 +1625,7 @@
def test_multiple_partial_args(self) -> None:
"Make sure partials remember locked-in args."
- ast_node = astroid.extract_node(
- """
+ ast_node = astroid.extract_node("""
from functools import partial
def test(a, b, c, d, e=5):
return a + b + c + d + e
@@ -1925,8 +1633,7 @@
test2 = partial(test1, 2)
test3 = partial(test2, 3)
test3(4, e=6) #@
- """
- )
+ """)
expected_args = [1, 2, 3, 4]
expected_keywords = {"e": 6}
@@ -1944,62 +1651,52 @@
def test_http_client_brain() -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
from http.client import OK
OK
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, astroid.Instance)
def test_http_status_brain() -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
import http
http.HTTPStatus.CONTINUE.phrase
- """
- )
+ """)
inferred = next(node.infer())
# Cannot infer the exact value but the field is there.
assert inferred.value == ""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
import http
http.HTTPStatus(200).phrase
- """
- )
+ """)
inferred = next(node.infer())
- assert isinstance(inferred, astroid.Const)
+ assert isinstance(inferred, nodes.Const)
def test_http_status_brain_iterable() -> None:
"""Astroid inference of `http.HTTPStatus` is an iterable subclass of `enum.IntEnum`"""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
import http
http.HTTPStatus
- """
- )
+ """)
inferred = next(node.infer())
assert "enum.IntEnum" in [ancestor.qname() for ancestor in inferred.ancestors()]
assert inferred.getattr("__iter__")
def test_oserror_model() -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
try:
1/0
except OSError as exc:
exc #@
- """
- )
+ """)
inferred = next(node.infer())
strerror = next(inferred.igetattr("strerror"))
- assert isinstance(strerror, astroid.Const)
+ assert isinstance(strerror, nodes.Const)
assert strerror.value == ""
@@ -2020,9 +1717,9 @@
@pytest.mark.parametrize(
"code,expected_class,expected_value",
[
- ("'hey'.encode()", astroid.Const, b""),
- ("b'hey'.decode()", astroid.Const, ""),
- ("'hey'.encode().decode()", astroid.Const, ""),
+ ("'hey'.encode()", nodes.Const, b""),
+ ("b'hey'.decode()", nodes.Const, ""),
+ ("'hey'.encode().decode()", nodes.Const, ""),
],
)
def test_str_and_bytes(code, expected_class, expected_value):
@@ -2039,14 +1736,12 @@
This test should only raise an InferenceError and no RecursionError.
"""
with pytest.raises(InferenceError):
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
class Crash:
def __len__(self) -> int:
return len(self)
len(Crash()) #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
node.inferred()
@@ -2058,8 +1753,7 @@
This test should succeed without any error.
"""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
class A:
def __len__(self) -> int:
return 42
@@ -2070,8 +1764,7 @@
return len(a)
len(Crash()) #@
- """
- )
+ """)
inferred = node.inferred()
assert len(inferred) == 1
assert isinstance(inferred[0], nodes.Const)
@@ -2086,8 +1779,7 @@
This test should only raise an InferenceError and no AttributeError.
"""
with pytest.raises(InferenceError):
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
class MyClass:
def some_func(self):
return lambda: 42
@@ -2096,7 +1788,6 @@
return len(self.some_func())
len(MyClass()) #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
node.inferred()
diff --git a/tests/brain/test_builtin.py b/tests/brain/test_builtin.py
index 6c49a80..85e1a1c 100644
--- a/tests/brain/test_builtin.py
+++ b/tests/brain/test_builtin.py
@@ -14,14 +14,12 @@
class BuiltinsTest(unittest.TestCase):
def test_infer_property(self):
- property_assign = _extract_single_node(
- """
+ property_assign = _extract_single_node("""
class Something:
def getter():
return 5
asd = property(getter) #@
- """
- )
+ """)
inferred_property = next(iter(property_assign.value.infer()))
self.assertTrue(isinstance(inferred_property, objects.Property))
class_parent = property_assign.scope()
@@ -139,8 +137,7 @@
def test_string_format_in_dataclass_pylint8109(self) -> None:
"""https://github.com/pylint-dev/pylint/issues/8109"""
- function_def = extract_node(
- """
+ function_def = extract_node("""
from dataclasses import dataclass
@dataclass
@@ -151,7 +148,6 @@
def __str__(self): #@
number_format = "{:,.%sf}" % self.round
return number_format.format(self.amount).rstrip("0").rstrip(".")
-"""
- )
+""")
inferit = function_def.infer_call_result(function_def, context=None)
assert [a.name for a in inferit] == [util.Uninferable]
diff --git a/tests/brain/test_dataclasses.py b/tests/brain/test_dataclasses.py
index cd3fcb4..4795327 100644
--- a/tests/brain/test_dataclasses.py
+++ b/tests/brain/test_dataclasses.py
@@ -6,7 +6,6 @@
import astroid
from astroid import bases, nodes
-from astroid.const import PY310_PLUS
from astroid.exceptions import InferenceError
from astroid.util import Uninferable
@@ -21,8 +20,7 @@
Note that the argument to the constructor is ignored by the inference.
"""
- klass, instance = astroid.extract_node(
- f"""
+ klass, instance = astroid.extract_node(f"""
from {module} import dataclass
@dataclass
@@ -31,8 +29,7 @@
A.name #@
A('hi').name #@
- """
- )
+ """)
with pytest.raises(InferenceError):
klass.inferred()
@@ -45,8 +42,7 @@
@parametrize_module
def test_inference_non_field_default(module: str):
"""Test inference of dataclass attribute with a non-field default."""
- klass, instance = astroid.extract_node(
- f"""
+ klass, instance = astroid.extract_node(f"""
from {module} import dataclass
@dataclass
@@ -55,8 +51,7 @@
A.name #@
A().name #@
- """
- )
+ """)
inferred = klass.inferred()
assert len(inferred) == 1
assert isinstance(inferred[0], nodes.Const)
@@ -76,8 +71,7 @@
"""Test inference of dataclass attribute with a field call default
(default keyword argument given).
"""
- klass, instance = astroid.extract_node(
- f"""
+ klass, instance = astroid.extract_node(f"""
from {module} import dataclass
from dataclasses import field
@@ -87,8 +81,7 @@
A.name #@
A().name #@
- """
- )
+ """)
inferred = klass.inferred()
assert len(inferred) == 1
assert isinstance(inferred[0], nodes.Const)
@@ -108,8 +101,7 @@
"""Test inference of dataclass attribute with a field call default
(default_factory keyword argument given).
"""
- klass, instance = astroid.extract_node(
- f"""
+ klass, instance = astroid.extract_node(f"""
from {module} import dataclass
from dataclasses import field
@@ -119,8 +111,7 @@
A.name #@
A().name #@
- """
- )
+ """)
inferred = klass.inferred()
assert len(inferred) == 1
assert isinstance(inferred[0], nodes.List)
@@ -142,8 +133,7 @@
Based on https://github.com/pylint-dev/pylint/issues/2600
"""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from typing import Dict
from {module} import dataclass
from dataclasses import field
@@ -159,8 +149,7 @@
for key, value in f():
print(key)
print(value)
- """
- )
+ """)
inferred = next(node.value.infer())
assert isinstance(inferred, bases.BoundMethod)
@@ -170,8 +159,7 @@
"""Test that class variables without type annotations are not
turned into instance attributes.
"""
- class_def, klass, instance = astroid.extract_node(
- f"""
+ class_def, klass, instance = astroid.extract_node(f"""
from {module} import dataclass
@dataclass
@@ -181,8 +169,7 @@
A #@
A.name #@
A().name #@
- """
- )
+ """)
inferred = next(class_def.infer())
assert isinstance(inferred, nodes.ClassDef)
assert inferred.instance_attrs == {}
@@ -202,8 +189,7 @@
"""Test that class variables with a ClassVar type annotations are not
turned into instance attributes.
"""
- class_def, klass, instance = astroid.extract_node(
- f"""
+ class_def, klass, instance = astroid.extract_node(f"""
from {module} import dataclass
from typing import ClassVar
@@ -214,8 +200,7 @@
A #@
A.name #@
A().name #@
- """
- )
+ """)
inferred = next(class_def.infer())
assert isinstance(inferred, nodes.ClassDef)
assert inferred.instance_attrs == {}
@@ -235,8 +220,7 @@
"""Test that class variables with InitVar type annotations are not
turned into instance attributes.
"""
- class_def, klass, instance = astroid.extract_node(
- f"""
+ class_def, klass, instance = astroid.extract_node(f"""
from {module} import dataclass
from dataclasses import InitVar
@@ -247,8 +231,7 @@
A #@
A.name #@
A().name #@
- """
- )
+ """)
inferred = next(class_def.infer())
assert isinstance(inferred, nodes.ClassDef)
assert inferred.instance_attrs == {}
@@ -268,8 +251,7 @@
"""Test that an attribute with a generic collection type from the
typing module is inferred correctly.
"""
- attr_nodes = astroid.extract_node(
- f"""
+ attr_nodes = astroid.extract_node(f"""
from {module} import dataclass
from dataclasses import field
import typing
@@ -288,8 +270,7 @@
a.list_prop #@
a.set_prop #@
a.tuple_prop #@
- """
- )
+ """)
names = (
"Dict",
"FrozenSet",
@@ -318,8 +299,7 @@
See issue #1129 and pylint-dev/pylint#4895
"""
- instance = astroid.extract_node(
- f"""
+ instance = astroid.extract_node(f"""
from {module} import dataclass
from {typing_module} import Any, Callable
@@ -328,8 +308,7 @@
enabled: Callable[[Any], bool]
A(lambda x: x == 42).enabled #@
- """
- )
+ """)
inferred = next(instance.infer())
assert inferred is Uninferable
@@ -337,8 +316,7 @@
@parametrize_module
def test_inference_inherited(module: str):
"""Test that an attribute is inherited from a superclass dataclass."""
- klass1, instance1, klass2, instance2 = astroid.extract_node(
- f"""
+ klass1, instance1, klass2, instance2 = astroid.extract_node(f"""
from {module} import dataclass
@dataclass
@@ -354,8 +332,7 @@
B(1).value #@
B.name #@
B(1).name #@
- """
- )
+ """)
with pytest.raises(InferenceError): # B.value is not defined
klass1.inferred()
@@ -378,8 +355,7 @@
def test_dataclass_order_of_inherited_attributes():
"""Test that an attribute in a child does not get put at the end of the init."""
- child, normal, keyword_only = astroid.extract_node(
- """
+ child, normal, keyword_only = astroid.extract_node("""
from dataclass import dataclass
@@ -416,25 +392,17 @@
Child.__init__ #@
NormalChild.__init__ #@
KeywordOnlyChild.__init__ #@
- """
- )
+ """)
child_init: bases.UnboundMethod = next(child.infer())
assert [a.name for a in child_init.args.args] == ["self", "a", "b", "c"]
normal_init: bases.UnboundMethod = next(normal.infer())
- if PY310_PLUS:
- assert [a.name for a in normal_init.args.args] == ["self", "a", "c"]
- assert [a.name for a in normal_init.args.kwonlyargs] == ["b"]
- else:
- assert [a.name for a in normal_init.args.args] == ["self", "a", "b", "c"]
- assert [a.name for a in normal_init.args.kwonlyargs] == []
+ assert [a.name for a in normal_init.args.args] == ["self", "a", "c"]
+ assert [a.name for a in normal_init.args.kwonlyargs] == ["b"]
keyword_only_init: bases.UnboundMethod = next(keyword_only.infer())
- if PY310_PLUS:
- assert [a.name for a in keyword_only_init.args.args] == ["self"]
- assert [a.name for a in keyword_only_init.args.kwonlyargs] == ["a", "b", "c"]
- else:
- assert [a.name for a in keyword_only_init.args.args] == ["self", "a", "b", "c"]
+ assert [a.name for a in keyword_only_init.args.args] == ["self"]
+ assert [a.name for a in keyword_only_init.args.kwonlyargs] == ["a", "b", "c"]
def test_pydantic_field() -> None:
@@ -442,8 +410,7 @@
(Eventually, we can extend the brain to support pydantic.Field)
"""
- klass, instance = astroid.extract_node(
- """
+ klass, instance = astroid.extract_node("""
from pydantic import Field
from pydantic.dataclasses import dataclass
@@ -453,8 +420,7 @@
A.name #@
A().name #@
- """
- )
+ """)
inferred = klass.inferred()
assert len(inferred) == 1
@@ -470,8 +436,7 @@
@parametrize_module
def test_init_empty(module: str):
"""Test init for a dataclass with no attributes."""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from {module} import dataclass
@dataclass
@@ -479,8 +444,7 @@
pass
A.__init__ #@
- """
- )
+ """)
init = next(node.infer())
assert [a.name for a in init.args.args] == ["self"]
@@ -488,8 +452,7 @@
@parametrize_module
def test_init_no_defaults(module: str):
"""Test init for a dataclass with attributes and no defaults."""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from {module} import dataclass
from typing import List
@@ -500,8 +463,7 @@
z: List[bool]
A.__init__ #@
- """
- )
+ """)
init = next(node.infer())
assert [a.name for a in init.args.args] == ["self", "x", "y", "z"]
assert [a.as_string() if a else None for a in init.args.annotations] == [
@@ -515,8 +477,7 @@
@parametrize_module
def test_init_defaults(module: str):
"""Test init for a dataclass with attributes and some defaults."""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from {module} import dataclass
from dataclasses import field
from typing import List
@@ -529,8 +490,7 @@
z: List[bool] = field(default_factory=list)
A.__init__ #@
- """
- )
+ """)
init = next(node.infer())
assert [a.name for a in init.args.args] == ["self", "w", "x", "y", "z"]
assert [a.as_string() if a else None for a in init.args.annotations] == [
@@ -550,8 +510,7 @@
@parametrize_module
def test_init_initvar(module: str):
"""Test init for a dataclass with attributes and an InitVar."""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from {module} import dataclass
from dataclasses import InitVar
from typing import List
@@ -564,8 +523,7 @@
z: List[bool]
A.__init__ #@
- """
- )
+ """)
init = next(node.infer())
assert [a.name for a in init.args.args] == ["self", "x", "y", "init_var", "z"]
assert [a.as_string() if a else None for a in init.args.annotations] == [
@@ -582,8 +540,7 @@
"""Test that no init is generated when init=False is passed to
dataclass decorator.
"""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from {module} import dataclass
from typing import List
@@ -594,8 +551,7 @@
z: List[bool]
A.__init__ #@
- """
- )
+ """)
init = next(node.infer())
assert init._proxied.parent.name == "object"
@@ -605,8 +561,7 @@
"""Test init for a dataclass with attributes with a field value where init=False
(these attributes should not be included in the initializer).
"""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from {module} import dataclass
from dataclasses import field
from typing import List
@@ -618,8 +573,7 @@
z: List[bool] = field(init=False)
A.__init__ #@
- """
- )
+ """)
init = next(node.infer())
assert [a.name for a in init.args.args] == ["self", "x", "y"]
assert [a.as_string() if a else None for a in init.args.annotations] == [
@@ -635,8 +589,7 @@
Based on https://github.com/pylint-dev/pylint/issues/3201
"""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from {module} import dataclass
from typing import List
@@ -652,8 +605,7 @@
arg2: str = None
B.__init__ #@
- """
- )
+ """)
init = next(node.infer())
assert [a.name for a in init.args.args] == ["self", "arg1", "arg2"]
assert [a.as_string() if a else None for a in init.args.annotations] == [
@@ -670,8 +622,7 @@
Based on https://github.com/pylint-dev/pylint/issues/3201
"""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from {module} import dataclass
from typing import List
@@ -686,8 +637,7 @@
arg2: list # Overrides arg2 from A
B.__init__ #@
- """
- )
+ """)
init = next(node.infer())
assert [a.name for a in init.args.args] == ["self", "arg0", "arg2", "arg1"]
assert [a.as_string() if a else None for a in init.args.annotations] == [
@@ -703,8 +653,7 @@
"""Test that astroid doesn't generate an initializer when attribute order is
invalid.
"""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from {module} import dataclass
@dataclass
@@ -713,8 +662,7 @@
arg2: str
A.__init__ #@
- """
- )
+ """)
init = next(node.infer())
assert init._proxied.parent.name == "object"
@@ -724,16 +672,14 @@
"""Test inference of dataclass attribute with a field call in another function
call.
"""
- node = astroid.extract_node(
- f"""
+ node = astroid.extract_node(f"""
from {module} import dataclass, field
from typing import cast
@dataclass
class A:
attribute: int = cast(int, field(default_factory=dict))
- """
- )
+ """)
inferred = node.inferred()
assert len(inferred) == 1 and isinstance(inferred[0], nodes.ClassDef)
assert "attribute" in inferred[0].instance_attrs
@@ -743,15 +689,13 @@
@parametrize_module
def test_invalid_field_call(module: str) -> None:
"""Test inference of invalid field call doesn't crash."""
- code = astroid.extract_node(
- f"""
+ code = astroid.extract_node(f"""
from {module} import dataclass, field
@dataclass
class A:
val: field()
- """
- )
+ """)
inferred = code.inferred()
assert len(inferred) == 1
assert isinstance(inferred[0], nodes.ClassDef)
@@ -760,8 +704,7 @@
def test_non_dataclass_is_not_dataclass() -> None:
"""Test that something that isn't a dataclass has the correct attribute."""
- module = astroid.parse(
- """
+ module = astroid.parse("""
class A:
val: field()
@@ -771,8 +714,7 @@
@dataclass
class B:
val: field()
- """
- )
+ """)
class_a = module.body[0].inferred()
assert len(class_a) == 1
assert isinstance(class_a[0], nodes.ClassDef)
@@ -786,8 +728,7 @@
def test_kw_only_sentinel() -> None:
"""Test that the KW_ONLY sentinel doesn't get added to the fields."""
- node_one, node_two = astroid.extract_node(
- """
+ node_one, node_two = astroid.extract_node("""
from dataclasses import dataclass, KW_ONLY
from dataclasses import KW_ONLY as keyword_only
@@ -804,12 +745,8 @@
y: str
B.__init__ #@
- """
- )
- if PY310_PLUS:
- expected = ["self", "y"]
- else:
- expected = ["self", "_", "y"]
+ """)
+ expected = ["self", "y"]
init = next(node_one.infer())
assert [a.name for a in init.args.args] == expected
@@ -818,12 +755,8 @@
def test_kw_only_decorator() -> None:
- """Test that we update the signature correctly based on the keyword.
-
- kw_only was introduced in PY310.
- """
- foodef, bardef, cee, dee = astroid.extract_node(
- """
+ """Test that we update the signature correctly based on the keyword."""
+ foodef, bardef, cee, dee = astroid.extract_node("""
from dataclasses import dataclass
@dataclass(kw_only=True)
@@ -851,54 +784,29 @@
Bar.__init__ #@
Cee.__init__ #@
Dee.__init__ #@
- """
- )
+ """)
foo_init: bases.UnboundMethod = next(foodef.infer())
- if PY310_PLUS:
- assert [a.name for a in foo_init.args.args] == ["self"]
- assert [a.name for a in foo_init.args.kwonlyargs] == ["a", "e"]
- else:
- assert [a.name for a in foo_init.args.args] == ["self", "a", "e"]
- assert [a.name for a in foo_init.args.kwonlyargs] == []
+ assert [a.name for a in foo_init.args.args] == ["self"]
+ assert [a.name for a in foo_init.args.kwonlyargs] == ["a", "e"]
bar_init: bases.UnboundMethod = next(bardef.infer())
- if PY310_PLUS:
- assert [a.name for a in bar_init.args.args] == ["self", "c"]
- assert [a.name for a in bar_init.args.kwonlyargs] == ["a", "e"]
- else:
- assert [a.name for a in bar_init.args.args] == ["self", "a", "e", "c"]
- assert [a.name for a in bar_init.args.kwonlyargs] == []
+ assert [a.name for a in bar_init.args.args] == ["self", "c"]
+ assert [a.name for a in bar_init.args.kwonlyargs] == ["a", "e"]
cee_init: bases.UnboundMethod = next(cee.infer())
- if PY310_PLUS:
- assert [a.name for a in cee_init.args.args] == ["self", "c", "d"]
- assert [a.name for a in cee_init.args.kwonlyargs] == ["a", "e"]
- else:
- assert [a.name for a in cee_init.args.args] == ["self", "a", "e", "c", "d"]
- assert [a.name for a in cee_init.args.kwonlyargs] == []
+ assert [a.name for a in cee_init.args.args] == ["self", "c", "d"]
+ assert [a.name for a in cee_init.args.kwonlyargs] == ["a", "e"]
dee_init: bases.UnboundMethod = next(dee.infer())
- if PY310_PLUS:
- assert [a.name for a in dee_init.args.args] == ["self", "c", "d"]
- assert [a.name for a in dee_init.args.kwonlyargs] == ["a", "e", "ee"]
- else:
- assert [a.name for a in dee_init.args.args] == [
- "self",
- "a",
- "e",
- "c",
- "d",
- "ee",
- ]
- assert [a.name for a in dee_init.args.kwonlyargs] == []
+ assert [a.name for a in dee_init.args.args] == ["self", "c", "d"]
+ assert [a.name for a in dee_init.args.kwonlyargs] == ["a", "e", "ee"]
def test_kw_only_in_field_call() -> None:
"""Test that keyword only fields get correctly put at the end of the __init__."""
- first, second, third = astroid.extract_node(
- """
+ first, second, third = astroid.extract_node("""
from dataclasses import dataclass, field
@dataclass
@@ -917,8 +825,7 @@
Parent.__init__ #@
Child.__init__ #@
GrandChild.__init__ #@
- """
- )
+ """)
first_init: bases.UnboundMethod = next(first.infer())
assert [a.name for a in first_init.args.args] == ["self"]
@@ -942,8 +849,7 @@
Reported in https://github.com/pylint-dev/pylint/issues/7418
"""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
import dataclasses
from unknown import Unknown
@@ -954,8 +860,7 @@
pass
MyDataclass()
- """
- )
+ """)
assert next(node.infer())
@@ -965,8 +870,7 @@
Reported in https://github.com/pylint-dev/pylint/issues/7422
"""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
from dataclasses import dataclass, InitVar
@@ -977,8 +881,7 @@
config: InitVar = None
TestClass.__init__ #@
- """
- )
+ """)
init_def: bases.UnboundMethod = next(node.infer())
assert [a.name for a in init_def.args.args] == ["self", "config"]
@@ -989,8 +892,7 @@
Reported in https://github.com/pylint-dev/pylint/issues/7425
"""
- bad_node, good_node = astroid.extract_node(
- """
+ bad_node, good_node = astroid.extract_node("""
from dataclasses import dataclass
from typing import Union
@@ -1013,8 +915,7 @@
xyz: str = ""
GoodExampleClass.__init__ #@
- """
- )
+ """)
bad_init: bases.UnboundMethod = next(bad_node.infer())
assert bad_init.args.defaults
@@ -1031,8 +932,7 @@
Reported in https://github.com/pylint-dev/pylint/issues/7427
Reported in https://github.com/pylint-dev/pylint/issues/7434
"""
- first, second, overwritten, overwriting, mixed = astroid.extract_node(
- """
+ first, second, overwritten, overwriting, mixed = astroid.extract_node("""
from dataclasses import dataclass
@dataclass
@@ -1080,8 +980,7 @@
OverwrittenChild.__init__ #@
OverwritingChild.__init__ #@
ChildWithMixedParents.__init__ #@
- """
- )
+ """)
first_init: bases.UnboundMethod = next(first.infer())
assert [a.name for a in first_init.args.args] == ["self", "ef", "_abc", "ghi"]
@@ -1103,8 +1002,7 @@
assert [a.name for a in mixed_init.args.args] == ["self", "_abc", "ghi"]
assert [a.value for a in mixed_init.args.defaults] == [1, 3]
- first = astroid.extract_node(
- """
+ first = astroid.extract_node("""
from dataclasses import dataclass
@dataclass
@@ -1124,8 +1022,7 @@
...
GrandChild.__init__ #@
- """
- )
+ """)
first_init: bases.UnboundMethod = next(first.infer())
assert [a.name for a in first_init.args.args] == ["self", "required", "optional"]
@@ -1142,8 +1039,7 @@
Eventually it can be merged into test_dataclass_with_multiple_inheritance.
"""
- impossible = astroid.extract_node(
- """
+ impossible = astroid.extract_node("""
from dataclasses import dataclass
@dataclass
@@ -1167,16 +1063,14 @@
...
ImpossibleGrandChild() #@
- """
- )
+ """)
assert next(impossible.infer()) is Uninferable
def test_dataclass_with_field_init_is_false() -> None:
"""When init=False it shouldn't end up in the __init__."""
- first, second, second_child, third_child, third = astroid.extract_node(
- """
+ first, second, second_child, third_child, third = astroid.extract_node("""
from dataclasses import dataclass, field
@@ -1205,8 +1099,7 @@
SecondChild.__init__ #@
ThirdChild.__init__ #@
Third.__init__ #@
- """
- )
+ """)
first_init: bases.UnboundMethod = next(first.infer())
assert [a.name for a in first_init.args.args] == ["self", "a"]
@@ -1234,8 +1127,7 @@
Regression test against changes tested in test_dataclass_with_multiple_inheritance
"""
- first, second, third = astroid.extract_node(
- """
+ first, second, third = astroid.extract_node("""
from dataclasses import dataclass
@dataclass
@@ -1267,8 +1159,7 @@
FirstChild.__init__ #@
SecondChild.__init__ #@
ThirdChild.__init__ #@
- """
- )
+ """)
first_init: bases.UnboundMethod = next(first.infer())
assert [a.name for a in first_init.args.args] == ["self", "_abc"]
@@ -1285,8 +1176,7 @@
def test_dataclass_with_properties() -> None:
"""Tests for __init__ creation for dataclasses that use properties."""
- first, second, third = astroid.extract_node(
- """
+ first, second, third = astroid.extract_node("""
from dataclasses import dataclass
@dataclass
@@ -1311,8 +1201,7 @@
Dataclass.__init__ #@
ParentOne.__init__ #@
ParentTwo.__init__ #@
- """
- )
+ """)
first_init: bases.UnboundMethod = next(first.infer())
assert [a.name for a in first_init.args.args] == ["self", "attr"]
@@ -1326,8 +1215,7 @@
assert [a.name for a in third_init.args.args] == ["self", "attr"]
assert [a.value for a in third_init.args.defaults] == [1]
- fourth = astroid.extract_node(
- """
+ fourth = astroid.extract_node("""
from dataclasses import dataclass
@dataclass
@@ -1344,9 +1232,78 @@
pass
Dataclass.__init__ #@
- """
- )
+ """)
fourth_init: bases.UnboundMethod = next(fourth.infer())
assert [a.name for a in fourth_init.args.args] == ["self", "other_attr", "attr"]
assert [a.name for a in fourth_init.args.defaults] == ["Uninferable"]
+
+
+def test_dataclass_with_duplicate_bases_no_crash():
+ """Regression test for https://github.com/pylint-dev/astroid/issues/2628.
+
+ A dataclass inheriting from a class with duplicate bases in MRO
+ (e.g., Protocol appearing both directly and indirectly) should not
+ crash with DuplicateBasesError during AST transformation.
+ """
+ code = """
+ import dataclasses
+ from typing import TypeVar, Protocol
+
+ BaseT = TypeVar("BaseT")
+ T = TypeVar("T", bound=BaseT)
+
+ class ConfigBase(Protocol[BaseT]):
+ ...
+
+ class Config(ConfigBase[T], Protocol[T]):
+ ...
+
+ @dataclasses.dataclass
+ class DatasetConfig(Config[T]):
+ name: str = "default"
+
+ DatasetConfig.__init__ #@
+ """
+ node = astroid.extract_node(code)
+ # Should not raise DuplicateBasesError — graceful degradation instead
+ inferred = next(node.infer())
+ assert inferred is not None
+
+
+def test_dataclass_with_duplicate_bases_field_default():
+ """Regression test for _get_previous_field_default with broken MRO.
+
+ When a parent dataclass defines a field with a default and a child (with
+ duplicate bases in its MRO) re-annotates that field without a value,
+ _get_previous_field_default should not crash with DuplicateBasesError.
+
+ See https://github.com/pylint-dev/astroid/issues/2628.
+ """
+ code = """
+ import dataclasses
+ from typing import TypeVar, Protocol
+
+ BaseT = TypeVar("BaseT")
+ T = TypeVar("T", bound=BaseT)
+
+ class ConfigBase(Protocol[BaseT]):
+ ...
+
+ class Config(ConfigBase[T], Protocol[T]):
+ ...
+
+ @dataclasses.dataclass
+ class BaseConfig(Config[T]):
+ name: str = "default"
+
+ @dataclasses.dataclass
+ class ChildConfig(BaseConfig[T]):
+ name: str
+
+ ChildConfig.__init__ #@
+ """
+ node = astroid.extract_node(code)
+ # Should not raise DuplicateBasesError in _get_previous_field_default
+ inferred = next(node.infer())
+ assert inferred is not None
diff --git a/tests/brain/test_dateutil.py b/tests/brain/test_dateutil.py
index 68cf640..5a7ced2 100644
--- a/tests/brain/test_dateutil.py
+++ b/tests/brain/test_dateutil.py
@@ -19,11 +19,9 @@
@unittest.skipUnless(HAS_DATEUTIL, "This test requires the dateutil library.")
class DateutilBrainTest(unittest.TestCase):
def test_parser(self):
- module = builder.parse(
- """
+ module = builder.parse("""
from dateutil.parser import parse
d = parse('2000-01-01')
- """
- )
+ """)
d_type = next(module["d"].infer())
self.assertIn(d_type.qname(), {"_pydatetime.datetime", "datetime.datetime"})
diff --git a/tests/brain/test_enum.py b/tests/brain/test_enum.py
index 127b3ed..7c9afee 100644
--- a/tests/brain/test_enum.py
+++ b/tests/brain/test_enum.py
@@ -15,8 +15,7 @@
class EnumBrainTest(unittest.TestCase):
def test_simple_enum(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
import enum
class MyEnum(enum.Enum):
@@ -26,8 +25,7 @@
def mymethod(self, x):
return 5
- """
- )
+ """)
enumeration = next(module["MyEnum"].infer())
one = enumeration["one"]
@@ -38,26 +36,23 @@
self.assertIn("builtins.property", prop.decoratornames())
meth = one.getattr("mymethod")[0]
- self.assertIsInstance(meth, astroid.FunctionDef)
+ self.assertIsInstance(meth, nodes.FunctionDef)
def test_looks_like_enum_false_positive(self) -> None:
# Test that a class named Enumeration is not considered a builtin enum.
- module = builder.parse(
- """
+ module = builder.parse("""
class Enumeration(object):
def __init__(self, name, enum_list):
pass
test = 42
- """
- )
+ """)
enumeration = module["Enumeration"]
test = next(enumeration.igetattr("test"))
self.assertEqual(test.value, 42)
def test_user_enum_false_positive(self) -> None:
# Test that a user-defined class named Enum is not considered a builtin enum.
- ast_node = astroid.extract_node(
- """
+ ast_node = astroid.extract_node("""
class Enum:
pass
@@ -65,12 +60,11 @@
red = 1
Color.red #@
- """
- )
+ """)
assert isinstance(ast_node, nodes.NodeNG)
inferred = ast_node.inferred()
self.assertEqual(len(inferred), 1)
- self.assertIsInstance(inferred[0], astroid.Const)
+ self.assertIsInstance(inferred[0], nodes.Const)
self.assertEqual(inferred[0].value, 1)
def test_ignores_with_nodes_from_body_of_enum(self) -> None:
@@ -89,8 +83,7 @@
assert len(inferred.locals["err"]) == 1
def test_enum_multiple_base_classes(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
import enum
class Mixin:
@@ -98,8 +91,7 @@
class MyEnum(Mixin, enum.Enum):
one = 1
- """
- )
+ """)
enumeration = next(module["MyEnum"].infer())
one = enumeration["one"]
@@ -110,14 +102,12 @@
)
def test_int_enum(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
import enum
class MyEnum(enum.IntEnum):
one = 1
- """
- )
+ """)
enumeration = next(module["MyEnum"].infer())
one = enumeration["one"]
@@ -129,14 +119,12 @@
)
def test_enum_func_form_is_class_not_instance(self) -> None:
- cls, instance = builder.extract_node(
- """
+ cls, instance = builder.extract_node("""
from enum import Enum
f = Enum('Audience', ['a', 'b', 'c'])
f #@
f(1) #@
- """
- )
+ """)
inferred_cls = next(cls.infer())
self.assertIsInstance(inferred_cls, bases.Instance)
inferred_instance = next(instance.infer())
@@ -145,94 +133,81 @@
self.assertIsInstance(next(inferred_instance.igetattr("value")), nodes.Const)
def test_enum_func_form_iterable(self) -> None:
- instance = builder.extract_node(
- """
+ instance = builder.extract_node("""
from enum import Enum
Animal = Enum('Animal', 'ant bee cat dog')
Animal
- """
- )
+ """)
inferred = next(instance.infer())
self.assertIsInstance(inferred, astroid.Instance)
self.assertTrue(inferred.getattr("__iter__"))
def test_enum_func_form_subscriptable(self) -> None:
- instance, name = builder.extract_node(
- """
+ instance, name = builder.extract_node("""
from enum import Enum
Animal = Enum('Animal', 'ant bee cat dog')
Animal['ant'] #@
Animal['ant'].name #@
- """
- )
+ """)
instance = next(instance.infer())
self.assertIsInstance(instance, astroid.Instance)
inferred = next(name.infer())
- self.assertIsInstance(inferred, astroid.Const)
+ self.assertIsInstance(inferred, nodes.Const)
def test_enum_func_form_has_dunder_members(self) -> None:
- instance = builder.extract_node(
- """
+ instance = builder.extract_node("""
from enum import Enum
Animal = Enum('Animal', 'ant bee cat dog')
for i in Animal.__members__:
i #@
- """
- )
+ """)
instance = next(instance.infer())
- self.assertIsInstance(instance, astroid.Const)
+ self.assertIsInstance(instance, nodes.Const)
self.assertIsInstance(instance.value, str)
def test_infer_enum_value_as_the_right_type(self) -> None:
- string_value, int_value = builder.extract_node(
- """
+ string_value, int_value = builder.extract_node("""
from enum import Enum
class A(Enum):
a = 'a'
b = 1
A.a.value #@
A.b.value #@
- """
- )
+ """)
inferred_string = string_value.inferred()
assert any(
- isinstance(elem, astroid.Const) and elem.value == "a"
+ isinstance(elem, nodes.Const) and elem.value == "a"
for elem in inferred_string
)
inferred_int = int_value.inferred()
assert any(
- isinstance(elem, astroid.Const) and elem.value == 1 for elem in inferred_int
+ isinstance(elem, nodes.Const) and elem.value == 1 for elem in inferred_int
)
def test_mingled_single_and_double_quotes_does_not_crash(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from enum import Enum
class A(Enum):
a = 'x"y"'
A.a.value #@
- """
- )
+ """)
inferred_string = next(node.infer())
assert inferred_string.value == 'x"y"'
def test_special_characters_does_not_crash(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import enum
class Example(enum.Enum):
NULL = '\\N{NULL}'
Example.NULL.value
- """
- )
+ """)
inferred_string = next(node.infer())
assert inferred_string.value == "\N{NULL}"
def test_dont_crash_on_for_loops_in_body(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class Commands(IntEnum):
_ignore_ = 'Commands index'
@@ -244,14 +219,12 @@
Commands[f'DC{index + 1}'] = 0x11 + index, f'Device Control {index + 1}'
Commands
- """
- )
+ """)
inferred = next(node.infer())
- assert isinstance(inferred, astroid.ClassDef)
+ assert isinstance(inferred, nodes.ClassDef)
def test_enum_tuple_list_values(self) -> None:
- tuple_node, list_node = builder.extract_node(
- """
+ tuple_node, list_node = builder.extract_node("""
import enum
class MyEnum(enum.Enum):
@@ -259,12 +232,11 @@
b = [2, 4]
MyEnum.a.value #@
MyEnum.b.value #@
- """
- )
+ """)
inferred_tuple_node = next(tuple_node.infer())
inferred_list_node = next(list_node.infer())
- assert isinstance(inferred_tuple_node, astroid.Tuple)
- assert isinstance(inferred_list_node, astroid.List)
+ assert isinstance(inferred_tuple_node, nodes.Tuple)
+ assert isinstance(inferred_list_node, nodes.List)
assert inferred_tuple_node.as_string() == "(1, 2)"
assert inferred_list_node.as_string() == "[2, 4]"
@@ -345,8 +317,7 @@
assert inferred.pytype() == ".TrickyEnum.value"
def test_enum_subclass_member_name(self) -> None:
- ast_node = astroid.extract_node(
- """
+ ast_node = astroid.extract_node("""
from enum import Enum
class EnumSubclass(Enum):
@@ -356,17 +327,15 @@
red = 1
Color.red.name #@
- """
- )
+ """)
assert isinstance(ast_node, nodes.NodeNG)
inferred = ast_node.inferred()
self.assertEqual(len(inferred), 1)
- self.assertIsInstance(inferred[0], astroid.Const)
+ self.assertIsInstance(inferred[0], nodes.Const)
self.assertEqual(inferred[0].value, "red")
def test_enum_subclass_member_value(self) -> None:
- ast_node = astroid.extract_node(
- """
+ ast_node = astroid.extract_node("""
from enum import Enum
class EnumSubclass(Enum):
@@ -376,18 +345,16 @@
red = 1
Color.red.value #@
- """
- )
+ """)
assert isinstance(ast_node, nodes.NodeNG)
inferred = ast_node.inferred()
self.assertEqual(len(inferred), 1)
- self.assertIsInstance(inferred[0], astroid.Const)
+ self.assertIsInstance(inferred[0], nodes.Const)
self.assertEqual(inferred[0].value, 1)
def test_enum_subclass_member_method(self) -> None:
# See Pylint issue #2626
- ast_node = astroid.extract_node(
- """
+ ast_node = astroid.extract_node("""
from enum import Enum
class EnumSubclass(Enum):
@@ -398,12 +365,11 @@
red = 1
Color.red.hello_pylint() #@
- """
- )
+ """)
assert isinstance(ast_node, nodes.NodeNG)
inferred = ast_node.inferred()
self.assertEqual(len(inferred), 1)
- self.assertIsInstance(inferred[0], astroid.Const)
+ self.assertIsInstance(inferred[0], nodes.Const)
self.assertEqual(inferred[0].value, "red")
def test_enum_subclass_different_modules(self) -> None:
@@ -417,47 +383,41 @@
""",
"a",
)
- ast_node = astroid.extract_node(
- """
+ ast_node = astroid.extract_node("""
from a import EnumSubclass
class Color(EnumSubclass):
red = 1
Color.red.value #@
- """
- )
+ """)
assert isinstance(ast_node, nodes.NodeNG)
inferred = ast_node.inferred()
self.assertEqual(len(inferred), 1)
- self.assertIsInstance(inferred[0], astroid.Const)
+ self.assertIsInstance(inferred[0], nodes.Const)
self.assertEqual(inferred[0].value, 1)
def test_members_member_ignored(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
from enum import Enum
class Animal(Enum):
a = 1
__members__ = {}
Animal.__members__ #@
- """
- )
+ """)
inferred = next(ast_node.infer())
- self.assertIsInstance(inferred, astroid.Dict)
+ self.assertIsInstance(inferred, nodes.Dict)
self.assertTrue(inferred.locals)
def test_enum_as_renamed_import(self) -> None:
"""Originally reported in https://github.com/pylint-dev/pylint/issues/5776."""
- ast_node: nodes.Attribute = builder.extract_node(
- """
+ ast_node: nodes.Attribute = builder.extract_node("""
from enum import Enum as PyEnum
class MyEnum(PyEnum):
ENUM_KEY = "enum_value"
MyEnum.ENUM_KEY
- """
- )
+ """)
inferred = next(ast_node.infer())
assert isinstance(inferred, bases.Instance)
assert inferred._proxied.name == "ENUM_KEY"
@@ -476,21 +436,17 @@
"module_with_class_named_enum",
)
- attribute_nodes = astroid.extract_node(
- """
+ 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(
- """
+ 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.
@@ -513,8 +469,7 @@
Test that only enum members `MARS` and `radius` appear in the `__members__` container while
the attribute `mass` does not.
"""
- enum_class = astroid.extract_node(
- """
+ enum_class = astroid.extract_node("""
from enum import Enum
class Planet(Enum): #@
MARS = (1, 2)
@@ -526,8 +481,7 @@
self.radius = radius
Planet.MARS.value
- """
- )
+ """)
enum_members = next(enum_class.igetattr("__members__"))
assert len(enum_members.items) == 2
mars, radius = enum_members.items
@@ -540,8 +494,7 @@
Test that a user-defined enum class is inferred when it subclasses
another user-defined enum class.
"""
- enum_class_node, enum_member_value_node = astroid.extract_node(
- """
+ enum_class_node, enum_member_value_node = astroid.extract_node("""
import sys
from enum import Enum
@@ -558,8 +511,7 @@
Color.RED.value #@
- """
- )
+ """)
assert "RED" in enum_class_node.locals
enum_members = enum_class_node.locals["__members__"][0].items
@@ -575,8 +527,7 @@
Originally reported in https://github.com/pylint-dev/pylint/issues/9015
"""
- ast_node: nodes.Attribute = builder.extract_node(
- """
+ ast_node: nodes.Attribute = builder.extract_node("""
import enum
@@ -586,8 +537,7 @@
_ignore_ = ["BAZ"]
BAZ = 42
MyEnum.__members__
- """
- )
+ """)
inferred = next(ast_node.infer())
members_names = [const_node.value for const_node, name_obj in inferred.items]
assert members_names == ["FOO", "BAR", "BAZ"]
@@ -595,8 +545,7 @@
def test_enum_sunder_names(self) -> None:
"""Test that both `_name_` and `_value_` sunder names exist"""
- sunder_name, sunder_value = builder.extract_node(
- """
+ sunder_name, sunder_value = builder.extract_node("""
import enum
@@ -604,8 +553,7 @@
APPLE = 42
MyEnum.APPLE._name_ #@
MyEnum.APPLE._value_ #@
- """
- )
+ """)
inferred_name = next(sunder_name.infer())
assert inferred_name.value == "APPLE"
diff --git a/tests/brain/test_gi.py b/tests/brain/test_gi.py
new file mode 100644
index 0000000..67ee2e4
--- /dev/null
+++ b/tests/brain/test_gi.py
@@ -0,0 +1,57 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
+
+import unittest
+
+try:
+ import gi # pylint: disable=unused-import
+
+ HAS_GI = True
+except ImportError:
+ HAS_GI = False
+
+from astroid import builder, nodes
+
+
+@unittest.skipUnless(HAS_GI, "This test requires the gobject introspection library.")
+class GiBrainClassificationTest(unittest.TestCase):
+ """Test that gi functions are correctly classified."""
+
+ def _inferred_gi_symbol(self, namespace, version, symbol):
+ node = builder.extract_node(f"""
+ import gi
+
+ gi.require_version("{namespace}", "{version}")
+ from gi.repository import {namespace}
+ {namespace}.{symbol}
+ """)
+ return node.inferred()
+
+ def test_gi_function_classification(self):
+ """Test that global functions are correctly classified without the 'self' argument."""
+ inferred = self._inferred_gi_symbol("GLib", "2.0", "get_tmp_dir")
+ self.assertEqual(len(inferred), 1)
+ self.assertEqual(inferred[0].pytype(), "builtins.function")
+
+ funcdef = inferred[0].frame()
+ self.assertIsInstance(funcdef, nodes.FunctionDef)
+
+ args = funcdef.argnames()
+ if len(args) > 0:
+ self.assertNotEqual(args[0], "self")
+
+ def test_gi_method_classification(self):
+ """Test that methods are correctly classified and accept the 'self' argument."""
+ inferred = self._inferred_gi_symbol("GLib", "2.0", "String.append")
+ self.assertEqual(len(inferred), 1)
+ self.assertEqual(inferred[0].pytype(), "builtins.instancemethod")
+
+ funcdef = inferred[0].frame()
+ self.assertIsInstance(funcdef, nodes.FunctionDef)
+
+ self.assertIn(
+ funcdef.argnames()[0],
+ {"self", funcdef.args.vararg},
+ "Method does not accept 'self' as first argument",
+ )
diff --git a/tests/brain/test_helpers.py b/tests/brain/test_helpers.py
new file mode 100644
index 0000000..d30fd1d
--- /dev/null
+++ b/tests/brain/test_helpers.py
@@ -0,0 +1,85 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
+
+import pytest
+
+from astroid import extract_node, nodes
+from astroid.brain.helpers import is_class_var
+
+
+@pytest.mark.parametrize(
+ "code",
+ [
+ pytest.param(
+ """
+ from typing import ClassVar
+
+ foo: ClassVar[int]
+ """,
+ id="from-import",
+ ),
+ pytest.param(
+ """
+ from typing import ClassVar
+
+ foo: ClassVar
+ """,
+ id="bare-classvar",
+ ),
+ pytest.param(
+ """
+ import typing
+
+ foo: typing.ClassVar[int]
+ """,
+ id="module-import",
+ ),
+ ],
+)
+def test_is_class_var_returns_true(code):
+ node = extract_node(code)
+ assert isinstance(node, nodes.AnnAssign)
+ assert is_class_var(node.annotation)
+
+
+@pytest.mark.parametrize(
+ "code",
+ [
+ pytest.param(
+ """
+ from typing import Final
+
+ foo: Final[int]
+ """,
+ id="wrong-name",
+ ),
+ pytest.param(
+ """
+ from typing import ClassVar
+
+ foo: list[ClassVar[int]]
+ """,
+ id="classvar-not-outermost",
+ ),
+ pytest.param(
+ """
+ from typing import ClassVar
+ ClassVar = int
+
+ foo: ClassVar
+ """,
+ id="shadowed-name",
+ ),
+ pytest.param(
+ """
+ foo: ClassVar[int]
+ """,
+ id="missing-import",
+ ),
+ ],
+)
+def test_is_class_var_returns_false(code):
+ node = extract_node(code)
+ assert isinstance(node, nodes.AnnAssign)
+ assert not is_class_var(node.annotation)
diff --git a/tests/brain/test_multiprocessing.py b/tests/brain/test_multiprocessing.py
index e6a1da5..4160253 100644
--- a/tests/brain/test_multiprocessing.py
+++ b/tests/brain/test_multiprocessing.py
@@ -30,23 +30,19 @@
# Test that module attributes are working,
# especially on Python 3.4+, where they are obtained
# from a context.
- module = builder.extract_node(
- """
+ module = builder.extract_node("""
import multiprocessing
- """
- )
+ """)
assert isinstance(module, nodes.Import)
module = module.do_import_module("multiprocessing")
cpu_count = next(module.igetattr("cpu_count"))
self.assertIsInstance(cpu_count, astroid.BoundMethod)
def test_module_name(self) -> None:
- module = builder.extract_node(
- """
+ module = builder.extract_node("""
import multiprocessing
multiprocessing.SyncManager()
- """
- )
+ """)
inferred_sync_mgr = next(module.infer())
module = inferred_sync_mgr.root()
self.assertEqual(module.name, "multiprocessing.managers")
@@ -54,8 +50,7 @@
def test_multiprocessing_manager(self) -> None:
# Test that we have the proper attributes
# for a multiprocessing.managers.SyncManager
- module = builder.parse(
- """
+ module = builder.parse("""
import multiprocessing
manager = multiprocessing.Manager()
queue = manager.Queue()
@@ -72,8 +67,7 @@
value = manager.Value()
array = manager.Array()
namespace = manager.Namespace()
- """
- )
+ """)
ast_queue = next(module["queue"].infer())
self.assertEqual(ast_queue.qname(), f"{queue.__name__}.Queue")
diff --git a/tests/brain/test_named_tuple.py b/tests/brain/test_named_tuple.py
index 40a96c7..b82dfa4 100644
--- a/tests/brain/test_named_tuple.py
+++ b/tests/brain/test_named_tuple.py
@@ -6,21 +6,18 @@
import unittest
-import astroid
from astroid import builder, nodes, util
from astroid.exceptions import AttributeInferenceError
class NamedTupleTest(unittest.TestCase):
def test_namedtuple_base(self) -> None:
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
from collections import namedtuple
class X(namedtuple("X", ["a", "b", "c"])):
pass
- """
- )
+ """)
assert isinstance(klass, nodes.ClassDef)
self.assertEqual(
[anc.name for anc in klass.ancestors()], ["X", "tuple", "object"]
@@ -31,41 +28,35 @@
self.assertFalse(anc.parent is None)
def test_namedtuple_inference(self) -> None:
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
from collections import namedtuple
name = "X"
fields = ["a", "b", "c"]
class X(namedtuple(name, fields)):
pass
- """
- )
+ """)
assert isinstance(klass, nodes.ClassDef)
base = next(base for base in klass.ancestors() if base.name == "X")
self.assertSetEqual({"a", "b", "c"}, set(base.instance_attrs))
def test_namedtuple_inference_failure(self) -> None:
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
from collections import namedtuple
def foo(fields):
return __(namedtuple("foo", fields))
- """
- )
+ """)
self.assertIs(util.Uninferable, next(klass.infer()))
def test_namedtuple_advanced_inference(self) -> None:
# urlparse return an object of class ParseResult, which has a
# namedtuple call and a mixin as base classes
- result = builder.extract_node(
- """
+ result = builder.extract_node("""
from urllib.parse import urlparse
result = __(urlparse('gopher://'))
- """
- )
+ """)
instance = next(result.infer())
self.assertGreaterEqual(len(instance.getattr("scheme")), 1)
self.assertGreaterEqual(len(instance.getattr("port")), 1)
@@ -75,86 +66,72 @@
self.assertEqual(instance.name, "ParseResult")
def test_namedtuple_instance_attrs(self) -> None:
- result = builder.extract_node(
- """
+ result = builder.extract_node("""
from collections import namedtuple
namedtuple('a', 'a b c')(1, 2, 3) #@
- """
- )
+ """)
inferred = next(result.infer())
for name, attr in inferred.instance_attrs.items():
self.assertEqual(attr[0].attrname, name)
def test_namedtuple_uninferable_fields(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
x = [A] * 2
from collections import namedtuple
l = namedtuple('a', x)
l(1)
- """
- )
+ """)
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred)
def test_namedtuple_access_class_fields(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "field other")
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIn("field", inferred.locals)
self.assertIn("other", inferred.locals)
def test_namedtuple_rename_keywords(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "abc def", rename=True)
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIn("abc", inferred.locals)
self.assertIn("_1", inferred.locals)
def test_namedtuple_rename_duplicates(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "abc abc abc", rename=True)
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIn("abc", inferred.locals)
self.assertIn("_1", inferred.locals)
self.assertIn("_2", inferred.locals)
def test_namedtuple_rename_uninferable(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "a b c", rename=UNINFERABLE)
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIn("a", inferred.locals)
self.assertIn("b", inferred.locals)
self.assertIn("c", inferred.locals)
def test_namedtuple_func_form(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple(typename="Tuple", field_names="a b c", rename=UNINFERABLE)
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertEqual(inferred.name, "Tuple")
self.assertIn("a", inferred.locals)
@@ -162,13 +139,11 @@
self.assertIn("c", inferred.locals)
def test_namedtuple_func_form_args_and_kwargs(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", field_names="a b c", rename=UNINFERABLE)
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertEqual(inferred.name, "Tuple")
self.assertIn("a", inferred.locals)
@@ -176,16 +151,14 @@
self.assertIn("c", inferred.locals)
def test_namedtuple_bases_are_actually_names_not_nodes(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", field_names="a b c", rename=UNINFERABLE)
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
- self.assertIsInstance(inferred, astroid.ClassDef)
- self.assertIsInstance(inferred.bases[0], astroid.Name)
+ self.assertIsInstance(inferred, nodes.ClassDef)
+ self.assertIsInstance(inferred.bases[0], nodes.Name)
self.assertEqual(inferred.bases[0].name, "tuple")
def test_invalid_label_does_not_crash_inference(self) -> None:
@@ -196,113 +169,95 @@
"""
node = builder.extract_node(code)
inferred = next(node.infer())
- assert isinstance(inferred, astroid.ClassDef)
+ assert isinstance(inferred, nodes.ClassDef)
assert "b" not in inferred.locals
assert "c" not in inferred.locals
def test_no_rename_duplicates_does_not_crash_inference(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "abc abc")
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_no_rename_keywords_does_not_crash_inference(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "abc def")
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_no_rename_nonident_does_not_crash_inference(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "123 456")
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_no_rename_underscore_does_not_crash_inference(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "_1")
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_invalid_typename_does_not_crash_inference(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("123", "abc")
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_keyword_typename_does_not_crash_inference(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("while", "abc")
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_typeerror_does_not_crash_inference(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", [123, 456])
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
# namedtuple converts all arguments to strings so these should be too
# and catch on the isidentifier() check
self.assertIs(util.Uninferable, inferred)
def test_pathological_str_does_not_crash_inference(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from collections import namedtuple
class Invalid:
def __str__(self):
return 123 # will raise TypeError
Tuple = namedtuple("Tuple", [Invalid()])
Tuple #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred)
def test_name_as_typename(self) -> None:
"""Reported in https://github.com/pylint-dev/pylint/issues/7429 as a crash."""
- good_node, good_node_two, bad_node = builder.extract_node(
- """
+ good_node, good_node_two, bad_node = builder.extract_node("""
import collections
collections.namedtuple(typename="MyTuple", field_names=["birth_date", "city"]) #@
collections.namedtuple("MyTuple", field_names=["birth_date", "city"]) #@
collections.namedtuple(["birth_date", "city"], typename="MyTuple") #@
- """
- )
+ """)
good_inferred = next(good_node.infer())
assert isinstance(good_inferred, nodes.ClassDef)
good_node_two_inferred = next(good_node_two.infer())
diff --git a/tests/brain/test_nose.py b/tests/brain/test_nose.py
deleted file mode 100644
index 2b615c1..0000000
--- a/tests/brain/test_nose.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
-# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
-# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
-
-from __future__ import annotations
-
-import unittest
-import warnings
-
-import astroid
-from astroid import builder
-
-try:
- with warnings.catch_warnings():
- warnings.simplefilter("ignore", DeprecationWarning)
- import nose # pylint: disable=unused-import
- HAS_NOSE = True
-except ImportError:
- HAS_NOSE = False
-
-
-@unittest.skipUnless(HAS_NOSE, "This test requires nose library.")
-class NoseBrainTest(unittest.TestCase):
- def test_nose_tools(self):
- methods = builder.extract_node(
- """
- from nose.tools import assert_equal
- from nose.tools import assert_equals
- from nose.tools import assert_true
- assert_equal = assert_equal #@
- assert_true = assert_true #@
- assert_equals = assert_equals #@
- """
- )
- assert isinstance(methods, list)
- assert_equal = next(methods[0].value.infer())
- assert_true = next(methods[1].value.infer())
- assert_equals = next(methods[2].value.infer())
-
- self.assertIsInstance(assert_equal, astroid.BoundMethod)
- self.assertIsInstance(assert_true, astroid.BoundMethod)
- self.assertIsInstance(assert_equals, astroid.BoundMethod)
- self.assertEqual(assert_equal.qname(), "unittest.case.TestCase.assertEqual")
- self.assertEqual(assert_true.qname(), "unittest.case.TestCase.assertTrue")
- self.assertEqual(assert_equals.qname(), "unittest.case.TestCase.assertEqual")
diff --git a/tests/brain/test_pathlib.py b/tests/brain/test_pathlib.py
index 5aea8d3..3715ff1 100644
--- a/tests/brain/test_pathlib.py
+++ b/tests/brain/test_pathlib.py
@@ -5,25 +5,23 @@
import astroid
from astroid import bases
-from astroid.const import PY310_PLUS, PY313_PLUS
+from astroid.const import PY313
from astroid.util import Uninferable
def test_inference_parents() -> None:
"""Test inference of ``pathlib.Path.parents``."""
- name_node = astroid.extract_node(
- """
+ name_node = astroid.extract_node("""
from pathlib import Path
current_path = Path().resolve()
path_parents = current_path.parents
path_parents
- """
- )
+ """)
inferred = name_node.inferred()
assert len(inferred) == 1
assert isinstance(inferred[0], bases.Instance)
- if PY313_PLUS:
+ if PY313:
assert inferred[0].qname() == "builtins.tuple"
else:
assert inferred[0].qname() == "pathlib._PathParents"
@@ -31,19 +29,17 @@
def test_inference_parents_subscript_index() -> None:
"""Test inference of ``pathlib.Path.parents``, accessed by index."""
- path = astroid.extract_node(
- """
+ path = astroid.extract_node("""
from pathlib import Path
current_path = Path().resolve()
current_path.parents[2] #@
- """
- )
+ """)
inferred = path.inferred()
assert len(inferred) == 1
assert isinstance(inferred[0], bases.Instance)
- if PY313_PLUS:
+ if PY313:
assert inferred[0].qname() == "pathlib._local.Path"
else:
assert inferred[0].qname() == "pathlib.Path"
@@ -51,36 +47,29 @@
def test_inference_parents_subscript_slice() -> None:
"""Test inference of ``pathlib.Path.parents``, accessed by slice."""
- name_node = astroid.extract_node(
- """
+ name_node = astroid.extract_node("""
from pathlib import Path
current_path = Path().resolve()
parent_path = current_path.parents[:2]
parent_path
- """
- )
+ """)
inferred = name_node.inferred()
assert len(inferred) == 1
- if PY310_PLUS:
- assert isinstance(inferred[0], bases.Instance)
- assert inferred[0].qname() == "builtins.tuple"
- else:
- assert inferred[0] is Uninferable
+ assert isinstance(inferred[0], bases.Instance)
+ assert inferred[0].qname() == "builtins.tuple"
def test_inference_parents_subscript_not_path() -> None:
"""Test inference of other ``.parents`` subscripts is unaffected."""
- name_node = astroid.extract_node(
- """
+ name_node = astroid.extract_node("""
class A:
parents = 42
c = A()
error = c.parents[:2]
error
- """
- )
+ """)
inferred = name_node.inferred()
assert len(inferred) == 1
assert inferred[0] is Uninferable
diff --git a/tests/brain/test_pytest.py b/tests/brain/test_pytest.py
index a063f40..c448a30 100644
--- a/tests/brain/test_pytest.py
+++ b/tests/brain/test_pytest.py
@@ -8,12 +8,10 @@
def test_pytest() -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
import pytest
pytest #@
- """
- )
+ """)
module = next(ast_node.infer())
attrs = [
"deprecated_call",
diff --git a/tests/brain/test_regex.py b/tests/brain/test_regex.py
index 1313ea4..bd38c81 100644
--- a/tests/brain/test_regex.py
+++ b/tests/brain/test_regex.py
@@ -29,22 +29,18 @@
)
def test_regex_pattern_and_match_subscriptable(self):
"""Test regex.Pattern and regex.Match are subscriptable in PY39+."""
- node1 = builder.extract_node(
- """
+ node1 = builder.extract_node("""
import regex
regex.Pattern[str]
- """
- )
+ """)
inferred1 = next(node1.infer())
assert isinstance(inferred1, nodes.ClassDef)
assert isinstance(inferred1.getattr("__class_getitem__")[0], nodes.FunctionDef)
- node2 = builder.extract_node(
- """
+ node2 = builder.extract_node("""
import regex
regex.Match[str]
- """
- )
+ """)
inferred2 = next(node2.infer())
assert isinstance(inferred2, nodes.ClassDef)
assert isinstance(inferred2.getattr("__class_getitem__")[0], nodes.FunctionDef)
diff --git a/tests/brain/test_signal.py b/tests/brain/test_signal.py
index a9d4e86..3a0033a 100644
--- a/tests/brain/test_signal.py
+++ b/tests/brain/test_signal.py
@@ -4,7 +4,6 @@
"""Unit Tests for the signal brain module."""
-
import sys
import pytest
@@ -21,12 +20,10 @@
def test_enum(enum_name):
"""Tests that the signal module enums are handled by the brain."""
# Extract node for signal module enum from code
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
import signal
signal.{enum_name}
- """
- )
+ """)
# Check the extracted node
assert isinstance(node, nodes.NodeNG)
diff --git a/tests/brain/test_six.py b/tests/brain/test_six.py
index 45a40e5..71eea95 100644
--- a/tests/brain/test_six.py
+++ b/tests/brain/test_six.py
@@ -22,8 +22,7 @@
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
class SixBrainTest(unittest.TestCase):
def test_attribute_access(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
import six
six.moves.http_client #@
six.moves.urllib_parse #@
@@ -31,8 +30,7 @@
six.moves.urllib.request #@
from six.moves import StringIO
StringIO #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
http_client = next(ast_nodes[0].infer())
self.assertIsInstance(http_client, nodes.Module)
@@ -79,12 +77,10 @@
self.test_attribute_access()
def test_from_imports(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
from six.moves import http_client
http_client.HTTPSConnection #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
qname = "http.client.HTTPSConnection"
@@ -95,18 +91,15 @@
See pylint-dev/pylint#1640 for relevant issue
"""
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
from six.moves.urllib.parse import urlparse
urlparse #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.FunctionDef)
def test_with_metaclass_subclasses_inheritance(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
class A(type):
def test(cls):
return cls
@@ -119,8 +112,7 @@
pass
B #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
self.assertEqual(inferred.name, "B")
@@ -154,15 +146,13 @@
MANAGER.register_transform(nodes.ClassDef, transform_class)
try:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
import six
class A(six.with_metaclass(type, object)):
pass
A #@
- """
- )
+ """)
inferred = next(ast_node.infer())
assert getattr(inferred, "_test_transform", None) == 314
finally:
diff --git a/tests/brain/test_ssl.py b/tests/brain/test_ssl.py
index 418c589..641f11e 100644
--- a/tests/brain/test_ssl.py
+++ b/tests/brain/test_ssl.py
@@ -12,15 +12,13 @@
def test_ssl_brain() -> None:
"""Test ssl brain transform."""
- module = parse(
- """
+ module = parse("""
import ssl
ssl.PROTOCOL_TLSv1
ssl.VerifyMode
ssl.TLSVersion
ssl.VerifyMode.CERT_REQUIRED
- """
- )
+ """)
inferred_protocol = next(module.body[1].value.infer())
assert isinstance(inferred_protocol, nodes.Const)
@@ -49,13 +47,11 @@
@pytest.mark.skipif(not PY312_PLUS, reason="Uses new 3.12 constant")
def test_ssl_brain_py312() -> None:
"""Test ssl brain transform."""
- module = parse(
- """
+ module = parse("""
import ssl
ssl.OP_LEGACY_SERVER_CONNECT
ssl.Options.OP_LEGACY_SERVER_CONNECT
- """
- )
+ """)
inferred_constant = next(module.body[1].value.infer())
assert isinstance(inferred_constant, nodes.Const)
diff --git a/tests/brain/test_statistics.py b/tests/brain/test_statistics.py
new file mode 100644
index 0000000..6d18f27
--- /dev/null
+++ b/tests/brain/test_statistics.py
@@ -0,0 +1,60 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
+
+"""Tests for brain statistics module."""
+
+from __future__ import annotations
+
+import unittest
+
+from astroid import extract_node
+from astroid.util import Uninferable
+
+
+class StatisticsBrainTest(unittest.TestCase):
+ """Test the brain statistics module functionality."""
+
+ def test_statistics_quantiles_inference(self) -> None:
+ """Test that statistics.quantiles() returns Uninferable instead of empty list."""
+ node = extract_node("""
+ import statistics
+ statistics.quantiles(list(range(100)), n=4) #@
+ """)
+ inferred = list(node.infer())
+ self.assertEqual(len(inferred), 1)
+ self.assertIs(inferred[0], Uninferable)
+
+ def test_statistics_quantiles_different_args(self) -> None:
+ """Test statistics.quantiles with different arguments."""
+ node = extract_node("""
+ import statistics
+ statistics.quantiles([1, 2, 3, 4, 5], n=10, method='inclusive') #@
+ """)
+ inferred = list(node.infer())
+ self.assertEqual(len(inferred), 1)
+ self.assertIs(inferred[0], Uninferable)
+
+ def test_statistics_quantiles_assignment_unpacking(self) -> None:
+ """Test the specific case that was causing false positives."""
+ node = extract_node("""
+ import statistics
+ q1, q2, q3 = statistics.quantiles(list(range(100)), n=4) #@
+ """)
+ call_node = node.value
+ inferred = list(call_node.infer())
+ self.assertEqual(len(inferred), 1)
+ self.assertIs(inferred[0], Uninferable)
+
+ def test_other_statistics_functions_not_affected(self) -> None:
+ """Test that other statistics functions are not affected by our brain module."""
+ node = extract_node("""
+ import statistics
+ statistics.mean([1, 2, 3, 4, 5]) #@
+ """)
+ inferred = list(node.infer())
+ self.assertNotEqual(len(inferred), 0)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/brain/test_threading.py b/tests/brain/test_threading.py
index f757649..2fe2c7f 100644
--- a/tests/brain/test_threading.py
+++ b/tests/brain/test_threading.py
@@ -13,12 +13,10 @@
class ThreadingBrainTest(unittest.TestCase):
def test_lock(self) -> None:
- lock_instance = builder.extract_node(
- """
+ lock_instance = builder.extract_node("""
import threading
threading.Lock()
- """
- )
+ """)
inferred = next(lock_instance.infer())
self.assert_is_valid_lock(inferred)
@@ -38,12 +36,10 @@
self._test_lock_object("BoundedSemaphore")
def _test_lock_object(self, object_name: str) -> None:
- lock_instance = builder.extract_node(
- f"""
+ lock_instance = builder.extract_node(f"""
import threading
threading.{object_name}()
- """
- )
+ """)
inferred = next(lock_instance.infer())
self.assert_is_valid_lock(inferred)
diff --git a/tests/brain/test_typing.py b/tests/brain/test_typing.py
index 11b77b7..55b99d2 100644
--- a/tests/brain/test_typing.py
+++ b/tests/brain/test_typing.py
@@ -15,12 +15,10 @@
Test that an inferred `typing.TypeVar()` call produces a `nodes.ClassDef`
node.
"""
- call_node = builder.extract_node(
- """
+ call_node = builder.extract_node("""
from typing import TypeVar
TypeVar('My.Type')
- """
- )
+ """)
with pytest.raises(InferenceError):
call_node.inferred()
@@ -30,12 +28,10 @@
"""
Test that _alias() calls can be inferred.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from typing import _alias
x = _alias(int, float)
- """
- )
+ """)
assert isinstance(node, nodes.Assign)
assert isinstance(node.value, nodes.Call)
inferred = next(node.value.infer())
@@ -59,14 +55,32 @@
Test that _alias() calls with the incorrect number of arguments can be inferred.
"""
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
from typing import _alias
x = _alias({alias_args})
- """
- )
+ """)
assert isinstance(node, nodes.Assign)
assert isinstance(node.value, nodes.Call)
inferred = next(node.value.infer())
assert isinstance(inferred, bases.Instance)
assert inferred.name == "_SpecialGenericAlias"
+
+
+class TestSpecialAlias:
+ @pytest.mark.parametrize(
+ "code",
+ [
+ "_CallableType()",
+ "_TupleType()",
+ ],
+ )
+ def test_special_alias_no_crash_on_empty_args(self, code: str) -> None:
+ """
+ Regression test for: https://github.com/pylint-dev/astroid/issues/2772
+
+ Test that _CallableType() and _TupleType() calls with no arguments
+ do not cause an IndexError.
+ """
+ # Should not raise IndexError
+ module = builder.parse(code)
+ assert isinstance(module, nodes.Module)
diff --git a/tests/brain/test_typing_extensions.py b/tests/brain/test_typing_extensions.py
index 883428b..70f1ba1 100644
--- a/tests/brain/test_typing_extensions.py
+++ b/tests/brain/test_typing_extensions.py
@@ -29,13 +29,11 @@
reason="Need typing_extensions>=4.4.0 to test TypeVar",
)
def test_typing_extensions_types() -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
from typing_extensions import TypeVar
TypeVar('MyTypeVar', int, float, complex) #@
TypeVar('AnyStr', str, bytes) #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
assert isinstance(inferred, nodes.ClassDef)
diff --git a/tests/brain/test_unittest.py b/tests/brain/test_unittest.py
index 53f7d9f..b54dc8d 100644
--- a/tests/brain/test_unittest.py
+++ b/tests/brain/test_unittest.py
@@ -15,14 +15,12 @@
Tests that the IsolatedAsyncioTestCase class is statically imported
thanks to the brain_unittest module.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from unittest import IsolatedAsyncioTestCase
class TestClass(IsolatedAsyncioTestCase):
pass
- """
- )
+ """)
assert [n.qname() for n in node.ancestors()] == [
"unittest.async_case.IsolatedAsyncioTestCase",
"unittest.case.TestCase",
diff --git a/tests/test_builder.py b/tests/test_builder.py
index 9a84491..3985c48 100644
--- a/tests/test_builder.py
+++ b/tests/test_builder.py
@@ -123,8 +123,7 @@
@staticmethod
def test_decorated_class_lineno() -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
class A: # L2
...
@@ -138,8 +137,7 @@
)
class C: # L13
...
- """
- )
+ """)
ast_module: nodes.Module = builder.parse(code) # type: ignore[assignment]
@@ -161,8 +159,7 @@
@staticmethod
def test_class_with_docstring() -> None:
"""Test class nodes which only have docstrings."""
- code = textwrap.dedent(
- '''\
+ code = textwrap.dedent('''\
class A:
"""My docstring"""
var = 1
@@ -181,8 +178,7 @@
class E:
...
- '''
- )
+ ''')
ast_module = builder.parse(code)
@@ -214,8 +210,7 @@
@staticmethod
def test_function_with_docstring() -> None:
"""Test function defintions with only docstrings."""
- code = textwrap.dedent(
- '''\
+ code = textwrap.dedent('''\
def a():
"""My docstring"""
var = 1
@@ -236,8 +231,7 @@
"""My docstring
is long.
"""
- '''
- )
+ ''')
ast_module = builder.parse(code)
@@ -572,12 +566,10 @@
self.assertEqual({"print_function"}, mod.future_imports)
def test_two_future_imports(self) -> None:
- mod = builder.parse(
- """
+ mod = builder.parse("""
from __future__ import print_function
from __future__ import absolute_import
- """
- )
+ """)
self.assertEqual({"print_function", "absolute_import"}, mod.future_imports)
def test_inferred_build(self) -> None:
@@ -597,15 +589,13 @@
self.assertIn("type", lclass.locals)
def test_infer_can_assign_regular_object(self) -> None:
- mod = builder.parse(
- """
+ mod = builder.parse("""
class A:
pass
a = A()
a.value = "is set"
a.other = "is set"
- """
- )
+ """)
obj = list(mod.igetattr("a"))
self.assertEqual(len(obj), 1)
obj = obj[0]
@@ -614,15 +604,13 @@
self.assertIn("other", obj.instance_attrs)
def test_infer_can_assign_has_slots(self) -> None:
- mod = builder.parse(
- """
+ mod = builder.parse("""
class A:
__slots__ = ('value',)
a = A()
a.value = "is set"
a.other = "not set"
- """
- )
+ """)
obj = list(mod.igetattr("a"))
self.assertEqual(len(obj), 1)
obj = obj[0]
@@ -631,12 +619,10 @@
self.assertNotIn("other", obj.instance_attrs)
def test_infer_can_assign_no_classdict(self) -> None:
- mod = builder.parse(
- """
+ mod = builder.parse("""
a = object()
a.value = "not set"
- """
- )
+ """)
obj = list(mod.igetattr("a"))
self.assertEqual(len(obj), 1)
obj = obj[0]
@@ -700,21 +686,17 @@
self.assertEqual(chain.value, "None")
def test_not_implemented(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
NotImplemented #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, NotImplemented)
def test_type_comments_without_content(self) -> None:
- node = builder.parse(
- """
+ node = builder.parse("""
a = 1 # type: # any comment
- """
- )
+ """)
assert node
@@ -781,9 +763,7 @@
def test_function_locals(self) -> None:
"""Test the 'locals' dictionary of an astroid function."""
_locals = self.module["global_access"].locals
- self.assertEqual(len(_locals), 4)
- keys = sorted(_locals.keys())
- self.assertEqual(keys, ["i", "key", "local", "val"])
+ self.assertEqual(sorted(_locals.keys()), ["i", "key", "local", "val"])
def test_class_base_props(self) -> None:
"""Test base properties and method of an astroid class."""
@@ -804,14 +784,8 @@
def test_class_locals(self) -> None:
"""Test the 'locals' dictionary of an astroid class."""
module = self.module
- klass1 = module["YO"]
- locals1 = klass1.locals
- keys = sorted(locals1.keys())
assert_keys = ["__annotations__", "__init__", "__module__", "__qualname__", "a"]
- self.assertEqual(keys, assert_keys)
- klass2 = module["YOUPI"]
- locals2 = klass2.locals
- keys = locals2.keys()
+ self.assertEqual(sorted(module["YO"].locals.keys()), assert_keys)
assert_keys = [
"__annotations__",
"__init__",
@@ -822,50 +796,43 @@
"method",
"static_method",
]
- self.assertEqual(sorted(keys), assert_keys)
+ self.assertEqual(sorted(module["YOUPI"].locals.keys()), assert_keys)
def test_class_instance_attrs(self) -> None:
module = self.module
- klass1 = module["YO"]
- klass2 = module["YOUPI"]
- self.assertEqual(list(klass1.instance_attrs.keys()), ["yo"])
- self.assertEqual(list(klass2.instance_attrs.keys()), ["member"])
+ self.assertEqual(list(module["YO"].instance_attrs.keys()), ["yo"])
+ self.assertEqual(list(module["YOUPI"].instance_attrs.keys()), ["member"])
def test_class_basenames(self) -> None:
module = self.module
- klass1 = module["YO"]
- klass2 = module["YOUPI"]
- self.assertEqual(klass1.basenames, [])
- self.assertEqual(klass2.basenames, ["YO"])
+ self.assertEqual(module["YO"].basenames, [])
+ self.assertEqual(module["YOUPI"].basenames, ["YO"])
def test_method_base_props(self) -> None:
"""Test base properties and method of an astroid method."""
- klass2 = self.module["YOUPI"]
- # "normal" method
- method = klass2["method"]
+ method = self.module["YOUPI"]["method"]
self.assertEqual(method.name, "method")
self.assertEqual([n.name for n in method.args.args], ["self"])
assert isinstance(method.doc_node, nodes.Const)
self.assertEqual(method.doc_node.value, "method\n test")
self.assertEqual(method.fromlineno, 48)
self.assertEqual(method.type, "method")
- # class method
- method = klass2["class_method"]
+
+ def test_class_method_base_props(self) -> None:
+ method = self.module["YOUPI"]["class_method"]
self.assertEqual([n.name for n in method.args.args], ["cls"])
self.assertEqual(method.type, "classmethod")
- # static method
- method = klass2["static_method"]
+
+ def test_static_method_base_props(self) -> None:
+ method = self.module["YOUPI"]["static_method"]
self.assertEqual(method.args.args, [])
self.assertEqual(method.type, "staticmethod")
def test_method_locals(self) -> None:
"""Test the 'locals' dictionary of an astroid method."""
method = self.module["YOUPI"]["method"]
- _locals = method.locals
- keys = sorted(_locals)
# ListComp variables are not accessible outside
- self.assertEqual(len(_locals), 3)
- self.assertEqual(keys, ["autre", "local", "self"])
+ self.assertEqual(sorted(method.locals), ["a", "autre", "local", "self"])
def test_unknown_encoding(self) -> None:
with self.assertRaises(AstroidSyntaxError):
@@ -881,8 +848,7 @@
def test_parse_module_with_invalid_type_comments_does_not_crash():
- node = builder.parse(
- """
+ node = builder.parse("""
# op {
# name: "AssignAddVariableOp"
# input_arg {
@@ -900,8 +866,7 @@
# is_stateful: true
# }
a, b = 2
- """
- )
+ """)
assert isinstance(node, nodes.Module)
diff --git a/tests/test_constraint.py b/tests/test_constraint.py
index 63f6275..00a8018 100644
--- a/tests/test_constraint.py
+++ b/tests/test_constraint.py
@@ -3,20 +3,36 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Tests for inference involving constraints."""
+
from __future__ import annotations
+from unittest.mock import patch
+
import pytest
from astroid import builder, nodes
+from astroid.bases import Instance
from astroid.util import Uninferable
+def node_info(node: nodes.NodeNG) -> str:
+ return f"Inference of {node.as_string()!r} at line {node.lineno}"
+
+
def common_params(node: str) -> pytest.MarkDecorator:
return pytest.mark.parametrize(
("condition", "satisfy_val", "fail_val"),
(
(f"{node} is None", None, 3),
(f"{node} is not None", 3, None),
+ (f"{node}", 3, None),
+ (f"not {node}", None, 3),
+ (f"isinstance({node}, int)", 3, None),
+ (f"isinstance({node}, (int, str))", 3, None),
+ (f"{node} == 3", 3, None),
+ (f"{node} != 3", None, 3),
+ (f"3 == {node}", 3, None),
+ (f"3 != {node}", None, 3),
),
)
@@ -26,8 +42,7 @@
condition: str, satisfy_val: int | None, fail_val: int | None
) -> None:
"""Test constraint for a variable that is used in the first statement of an if body."""
- node1, node2 = builder.extract_node(
- f"""
+ node1, node2 = builder.extract_node(f"""
def f1(x = {fail_val}):
if {condition}: # Filters out default value
return (
@@ -39,8 +54,7 @@
return (
x #@
)
- """
- )
+ """)
inferred = node1.inferred()
assert len(inferred) == 1
@@ -61,8 +75,7 @@
"""Test constraint for a variable that is used in an if body with multiple
statements.
"""
- node1, node2 = builder.extract_node(
- f"""
+ node1, node2 = builder.extract_node(f"""
def f1(x = {fail_val}):
if {condition}: # Filters out default value
print(x)
@@ -76,8 +89,7 @@
return (
x #@
)
- """
- )
+ """)
inferred = node1.inferred()
assert len(inferred) == 1
@@ -96,8 +108,7 @@
condition: str, satisfy_val: int | None, fail_val: int | None
) -> None:
"""Test that constraint for a different variable doesn't apply."""
- nodes_ = builder.extract_node(
- f"""
+ nodes_ = builder.extract_node(f"""
def f1(x, y = {fail_val}):
if {condition}: # Does not filter out fail_val
return (
@@ -109,8 +120,7 @@
return (
y #@
)
- """
- )
+ """)
for node, val in zip(nodes_, (fail_val, satisfy_val)):
inferred = node.inferred()
assert len(inferred) == 2
@@ -125,8 +135,7 @@
condition: str, satisfy_val: int | None, fail_val: int | None
) -> None:
"""Test that constraint in an if condition doesn't apply outside of the if."""
- nodes_ = builder.extract_node(
- f"""
+ nodes_ = builder.extract_node(f"""
def f1(x = {fail_val}):
if {condition}:
pass
@@ -141,8 +150,7 @@
return (
x #@
)
- """
- )
+ """)
for node, val in zip(nodes_, (fail_val, satisfy_val)):
inferred = node.inferred()
assert len(inferred) == 2
@@ -157,8 +165,7 @@
condition: str, satisfy_val: int | None, fail_val: int | None
) -> None:
"""Test that constraint in an if condition applies within inner if statements."""
- node1, node2 = builder.extract_node(
- f"""
+ node1, node2 = builder.extract_node(f"""
def f1(y, x = {fail_val}):
if {condition}:
if y is not None:
@@ -172,8 +179,7 @@
return (
x #@
)
- """
- )
+ """)
inferred = node1.inferred()
assert len(inferred) == 1
assert inferred[0] is Uninferable
@@ -190,8 +196,7 @@
"""Test that when no inferred values satisfy all constraints, Uninferable is
inferred.
"""
- node1, node2 = builder.extract_node(
- """
+ node1, node2 = builder.extract_node("""
def f1():
x = None
if x is not None:
@@ -203,8 +208,7 @@
pass
else:
x #@
- """
- )
+ """)
inferred = node1.inferred()
assert len(inferred) == 1
assert inferred[0] is Uninferable
@@ -221,8 +225,7 @@
"""Test that constraint in an if condition doesn't apply when the variable
is assigned to a failing value inside the if body.
"""
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
def f(x, y):
if {condition}:
if y:
@@ -230,8 +233,7 @@
return (
x #@
)
- """
- )
+ """)
inferred = node.inferred()
assert len(inferred) == 2
assert inferred[0] is Uninferable
@@ -247,8 +249,7 @@
"""Test that constraint in an if condition is negated when the variable
is used in the elif and else branches.
"""
- node1, node2, node3, node4 = builder.extract_node(
- f"""
+ node1, node2, node3, node4 = builder.extract_node(f"""
def f1(y, x = {fail_val}):
if {condition}:
pass
@@ -272,20 +273,21 @@
return (
x #@
)
- """
- )
+ """)
for node in (node1, node2):
+ msg = node_info(node)
inferred = node.inferred()
- assert len(inferred) == 2
- assert isinstance(inferred[0], nodes.Const)
- assert inferred[0].value == fail_val
+ assert len(inferred) == 2, msg
+ assert isinstance(inferred[0], nodes.Const), msg
+ assert inferred[0].value == fail_val, msg
- assert inferred[1] is Uninferable
+ assert inferred[1] is Uninferable, msg
for node in (node3, node4):
+ msg = node_info(node)
inferred = node.inferred()
- assert len(inferred) == 1
- assert inferred[0] is Uninferable
+ assert len(inferred) == 1, msg
+ assert inferred[0] is Uninferable, msg
@common_params(node="x")
@@ -295,8 +297,7 @@
"""Test that constraint in an if condition doesn't apply when the variable
is assigned to a failing value inside the else branch.
"""
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
def f(x, y):
if {condition}:
return x
@@ -306,8 +307,7 @@
return (
x #@
)
- """
- )
+ """)
inferred = node.inferred()
assert len(inferred) == 2
assert inferred[0] is Uninferable
@@ -323,16 +323,14 @@
"""Test that constraint in an if condition doesn't apply when the variable
is shadowed by an inner comprehension scope.
"""
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
def f(x):
if {condition}:
return [
x #@
for x in [{satisfy_val}, {fail_val}]
]
- """
- )
+ """)
inferred = node.inferred()
assert len(inferred) == 2
@@ -348,16 +346,14 @@
"""Test that constraint in an if condition doesn't apply when the variable
is shadowed by an inner function scope.
"""
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
x = {satisfy_val}
if {condition}:
def f(x = {fail_val}):
return (
x #@
)
- """
- )
+ """)
inferred = node.inferred()
assert len(inferred) == 2
assert isinstance(inferred[0], nodes.Const)
@@ -373,16 +369,14 @@
"""Test that constraint in an if condition doesn't apply for a parameter
a different function call, but with the same name.
"""
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
def f(x = {satisfy_val}):
if {condition}:
g({fail_val}) #@
def g(x):
return x
- """
- )
+ """)
inferred = node.inferred()
assert len(inferred) == 1
assert isinstance(inferred[0], nodes.Const)
@@ -394,8 +388,7 @@
condition: str, satisfy_val: int | None, fail_val: int | None
) -> None:
"""Test constraint for an instance attribute in an if statement."""
- node1, node2 = builder.extract_node(
- f"""
+ node1, node2 = builder.extract_node(f"""
class A1:
def __init__(self, x = {fail_val}):
self.x = x
@@ -411,8 +404,7 @@
def method(self):
if {condition}:
self.x #@
- """
- )
+ """)
inferred = node1.inferred()
assert len(inferred) == 1
@@ -433,8 +425,7 @@
"""Test that constraint in an if condition doesn't apply to an instance attribute
when it is assigned inside the if body.
"""
- node1, node2 = builder.extract_node(
- f"""
+ node1, node2 = builder.extract_node(f"""
class A1:
def __init__(self, x):
self.x = x
@@ -448,8 +439,7 @@
if {condition}:
self.x = {fail_val}
self.x #@
- """
- )
+ """)
inferred = node1.inferred()
assert len(inferred) == 2
@@ -476,8 +466,7 @@
"""Test that constraint in an if condition doesn't apply to an instance attribute
when the constraint refers to a variable with the same name.
"""
- node1, node2 = builder.extract_node(
- f"""
+ node1, node2 = builder.extract_node(f"""
class A1:
def __init__(self, x = {fail_val}):
self.x = x
@@ -486,8 +475,7 @@
if {condition}:
x #@
self.x #@
- """
- )
+ """)
inferred = node1.inferred()
assert len(inferred) == 1
@@ -508,8 +496,7 @@
"""Test that constraint in an if condition doesn't apply to a variable with the same
name.
"""
- node1, node2 = builder.extract_node(
- f"""
+ node1, node2 = builder.extract_node(f"""
class A1:
def __init__(self, x = {fail_val}):
self.x = x
@@ -518,8 +505,7 @@
if {condition}:
x #@
self.x #@
- """
- )
+ """)
inferred = node1.inferred()
assert len(inferred) == 2
@@ -540,8 +526,7 @@
"""Test that constraint in an if condition doesn't apply to an instance attribute
for an object of a different class.
"""
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
class A1:
def __init__(self, x = {fail_val}):
self.x = x
@@ -554,8 +539,7 @@
class A2:
def __init__(self):
self.x = {fail_val}
- """
- )
+ """)
inferred = node.inferred()
assert len(inferred) == 1
@@ -570,8 +554,7 @@
"""Test that constraint in an if condition doesn't apply to a variable of the same name,
when that variable is used to infer the value of the instance attribute.
"""
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
class A1:
def __init__(self, x):
self.x = x
@@ -581,8 +564,7 @@
if {condition}:
self.x = x
self.x #@
- """
- )
+ """)
inferred = node.inferred()
assert len(inferred) == 2
@@ -590,3 +572,572 @@
assert isinstance(inferred[1], nodes.Const)
assert inferred[1].value == fail_val
+
+
+@common_params(node="x")
+def test_if_exp_body(
+ condition: str, satisfy_val: int | None, fail_val: int | None
+) -> None:
+ """Test constraint for a variable that is used in an if exp body."""
+ node1, node2 = builder.extract_node(f"""
+ def f1(x = {fail_val}):
+ return (
+ x if {condition} else None #@
+ )
+
+ def f2(x = {satisfy_val}):
+ return (
+ x if {condition} else None #@
+ )
+ """)
+
+ inferred = node1.body.inferred()
+ assert len(inferred) == 1
+ assert inferred[0] is Uninferable
+
+ inferred = node2.body.inferred()
+ assert len(inferred) == 2
+ assert isinstance(inferred[0], nodes.Const)
+ assert inferred[0].value == satisfy_val
+ assert inferred[1] is Uninferable
+
+
+@common_params(node="x")
+def test_if_exp_else(
+ condition: str, satisfy_val: int | None, fail_val: int | None
+) -> None:
+ """Test constraint for a variable that is used in an if exp else block."""
+ node1, node2 = builder.extract_node(f"""
+ def f1(x = {satisfy_val}):
+ return (
+ None if {condition} else x #@
+ )
+
+ def f2(x = {fail_val}):
+ return (
+ None if {condition} else x #@
+ )
+ """)
+
+ inferred = node1.orelse.inferred()
+ assert len(inferred) == 1
+ assert inferred[0] is Uninferable
+
+ inferred = node2.orelse.inferred()
+ assert len(inferred) == 2
+ assert isinstance(inferred[0], nodes.Const)
+ assert inferred[0].value == fail_val
+ assert inferred[1] is Uninferable
+
+
+@common_params(node="x")
+def test_outside_if_exp(
+ condition: str, satisfy_val: int | None, fail_val: int | None
+) -> None:
+ """Test that constraint in an if exp condition doesn't apply outside of the if exp."""
+ nodes_ = builder.extract_node(f"""
+ def f1(x = {fail_val}):
+ x if {condition} else None
+ return (
+ x #@
+ )
+
+ def f2(x = {satisfy_val}):
+ None if {condition} else x
+ return (
+ x #@
+ )
+ """)
+ for node, val in zip(nodes_, (fail_val, satisfy_val)):
+ inferred = node.inferred()
+ assert len(inferred) == 2
+ assert isinstance(inferred[0], nodes.Const)
+ assert inferred[0].value == val
+ assert inferred[1] is Uninferable
+
+
+@common_params(node="x")
+def test_nested_if_exp(
+ condition: str, satisfy_val: int | None, fail_val: int | None
+) -> None:
+ """Test that constraint in an if exp condition applies within inner if exp."""
+ node1, node2 = builder.extract_node(f"""
+ def f1(y, x = {fail_val}):
+ return (
+ (x if y else None) if {condition} else None #@
+ )
+
+ def f2(y, x = {satisfy_val}):
+ return (
+ (x if y else None) if {condition} else None #@
+ )
+ """)
+
+ inferred = node1.body.body.inferred()
+ assert len(inferred) == 1
+ assert inferred[0] is Uninferable
+
+ inferred = node2.body.body.inferred()
+ assert len(inferred) == 2
+ assert isinstance(inferred[0], nodes.Const)
+ assert inferred[0].value == satisfy_val
+ assert inferred[1] is Uninferable
+
+
+@common_params(node="self.x")
+def test_if_exp_instance_attr(
+ condition: str, satisfy_val: int | None, fail_val: int | None
+) -> None:
+ """Test constraint for an instance attribute in an if exp."""
+ node1, node2 = builder.extract_node(f"""
+ class A1:
+ def __init__(self, x = {fail_val}):
+ self.x = x
+
+ def method(self):
+ return (
+ self.x if {condition} else None #@
+ )
+
+ class A2:
+ def __init__(self, x = {satisfy_val}):
+ self.x = x
+
+ def method(self):
+ return (
+ self.x if {condition} else None #@
+ )
+ """)
+
+ inferred = node1.body.inferred()
+ assert len(inferred) == 1
+ assert inferred[0] is Uninferable
+
+ inferred = node2.body.inferred()
+ assert len(inferred) == 2
+ assert isinstance(inferred[0], nodes.Const)
+ assert inferred[0].value == satisfy_val
+ assert inferred[1].value is Uninferable
+
+
+@common_params(node="self.x")
+def test_if_exp_instance_attr_varname_collision(
+ condition: str, satisfy_val: int | None, fail_val: int | None
+) -> None:
+ """Test that constraint in an if exp condition doesn't apply to a variable with the same name."""
+ node = builder.extract_node(f"""
+ class A:
+ def __init__(self, x = {fail_val}):
+ self.x = x
+
+ def method(self, x = {fail_val}):
+ return (
+ x if {condition} else None #@
+ )
+ """)
+
+ inferred = node.body.inferred()
+ assert len(inferred) == 2
+ assert isinstance(inferred[0], nodes.Const)
+ assert inferred[0].value == fail_val
+ assert inferred[1].value is Uninferable
+
+
+def test_isinstance_equal_types() -> None:
+ """Test constraint for an object whose type is equal to the checked type."""
+ node = builder.extract_node("""
+ class A:
+ pass
+
+ x = A()
+
+ if isinstance(x, A):
+ x #@
+ """)
+
+ inferred = node.inferred()
+ assert len(inferred) == 1
+ assert isinstance(inferred[0], Instance)
+ assert isinstance(inferred[0]._proxied, nodes.ClassDef)
+ assert inferred[0].name == "A"
+
+
+def test_isinstance_subtype() -> None:
+ """Test constraint for an object whose type is a strict subtype of the checked type."""
+ node = builder.extract_node("""
+ class A:
+ pass
+
+ class B(A):
+ pass
+
+ x = B()
+
+ if isinstance(x, A):
+ x #@
+ """)
+
+ inferred = node.inferred()
+ assert len(inferred) == 1
+ assert isinstance(inferred[0], Instance)
+ assert isinstance(inferred[0]._proxied, nodes.ClassDef)
+ assert inferred[0].name == "B"
+
+
+def test_isinstance_unrelated_types():
+ """Test constraint for an object whose type is not related to the checked type."""
+ node = builder.extract_node("""
+ class A:
+ pass
+
+ class B:
+ pass
+
+ x = A()
+
+ if isinstance(x, B):
+ x #@
+ """)
+
+ inferred = node.inferred()
+ assert len(inferred) == 1
+ assert inferred[0] is Uninferable
+
+
+def test_isinstance_supertype():
+ """Test constraint for an object whose type is a strict supertype of the checked type."""
+ node = builder.extract_node("""
+ class A:
+ pass
+
+ class B(A):
+ pass
+
+ x = A()
+
+ if isinstance(x, B):
+ x #@
+ """)
+
+ inferred = node.inferred()
+ assert len(inferred) == 1
+ assert inferred[0] is Uninferable
+
+
+def test_isinstance_multiple_inheritance():
+ """Test constraint for an object that inherits from more than one parent class."""
+ n1, n2, n3 = builder.extract_node("""
+ class A:
+ pass
+
+ class B:
+ pass
+
+ class C(A, B):
+ pass
+
+ x = C()
+
+ if isinstance(x, C):
+ x #@
+
+ if isinstance(x, A):
+ x #@
+
+ if isinstance(x, B):
+ x #@
+ """)
+
+ for node in (n1, n2, n3):
+ msg = node_info(node)
+ inferred = node.inferred()
+ assert len(inferred) == 1, msg
+ assert isinstance(inferred[0], Instance), msg
+ assert isinstance(inferred[0]._proxied, nodes.ClassDef), msg
+ assert inferred[0].name == "C", msg
+
+
+def test_isinstance_diamond_inheritance():
+ """Test constraint for an object that inherits from parent classes
+ in diamond inheritance.
+ """
+ n1, n2, n3, n4 = builder.extract_node("""
+ class A():
+ pass
+
+ class B(A):
+ pass
+
+ class C(A):
+ pass
+
+ class D(B, C):
+ pass
+
+ x = D()
+
+ if isinstance(x, D):
+ x #@
+
+ if isinstance(x, B):
+ x #@
+
+ if isinstance(x, C):
+ x #@
+
+ if isinstance(x, A):
+ x #@
+ """)
+
+ for node in (n1, n2, n3, n4):
+ msg = node_info(node)
+ inferred = node.inferred()
+ assert len(inferred) == 1, msg
+ assert isinstance(inferred[0], Instance), msg
+ assert isinstance(inferred[0]._proxied, nodes.ClassDef), msg
+ assert inferred[0].name == "D", msg
+
+
+def test_isinstance_keyword_arguments():
+ """Test that constraint does not apply when `isinstance` is called
+ with keyword arguments.
+ """
+ n1, n2 = builder.extract_node("""
+ x = 3
+
+ if isinstance(object=x, classinfo=str):
+ x #@
+
+ if isinstance(x, str, object=x, classinfo=str):
+ x #@
+ """)
+
+ for node in (n1, n2):
+ msg = node_info(node)
+ inferred = node.inferred()
+ assert len(inferred) == 1, msg
+ assert isinstance(inferred[0], nodes.Const), msg
+ assert inferred[0].value == 3, msg
+
+
+def test_isinstance_extra_argument():
+ """Test that constraint does not apply when `isinstance` is called
+ with more than two positional arguments.
+ """
+ node = builder.extract_node("""
+ x = 3
+
+ if isinstance(x, str, bool):
+ x #@
+ """)
+
+ inferred = node.inferred()
+ assert len(inferred) == 1
+ assert isinstance(inferred[0], nodes.Const)
+ assert inferred[0].value == 3
+
+
+def test_isinstance_classinfo_inference_error():
+ """Test that constraint is satisfied when `isinstance` is called with
+ classinfo that raises an inference error.
+ """
+ node = builder.extract_node("""
+ x = 3
+
+ if isinstance(x, undefined_type):
+ x #@
+ """)
+
+ inferred = node.inferred()
+ assert len(inferred) == 1
+ assert isinstance(inferred[0], nodes.Const)
+ assert inferred[0].value == 3
+
+
+def test_isinstance_uninferable_classinfo():
+ """Test that constraint is satisfied when `isinstance` is called with
+ uninferable classinfo.
+ """
+ node = builder.extract_node("""
+ def f(classinfo):
+ x = 3
+
+ if isinstance(x, classinfo):
+ x #@
+ """)
+
+ inferred = node.inferred()
+ assert len(inferred) == 1
+ assert isinstance(inferred[0], nodes.Const)
+ assert inferred[0].value == 3
+
+
+def test_isinstance_mro_error():
+ """Test that constraint is satisfied when computing the object's
+ method resolution order raises an MRO error.
+ """
+ node = builder.extract_node("""
+ class A():
+ pass
+
+ class B(A, A):
+ pass
+
+ x = B()
+
+ if isinstance(x, A):
+ x #@
+ """)
+
+ inferred = node.inferred()
+ assert len(inferred) == 1
+ assert isinstance(inferred[0], Instance)
+ assert isinstance(inferred[0]._proxied, nodes.ClassDef)
+ assert inferred[0].name == "B"
+
+
+def test_isinstance_uninferable():
+ """Test that constraint is satisfied when `isinstance` inference returns Uninferable."""
+ node = builder.extract_node("""
+ x = 3
+
+ if isinstance(x, str):
+ x #@
+ """)
+
+ with patch(
+ "astroid.constraint.helpers.object_isinstance", return_value=Uninferable
+ ):
+ inferred = node.inferred()
+ assert len(inferred) == 1
+ assert isinstance(inferred[0], nodes.Const)
+ assert inferred[0].value == 3
+
+
+def test_equality_callable():
+ """Test constraint for equality of callables."""
+ node1, node2, node3, node4, node5, node6 = builder.extract_node("""
+ class Foo:
+ pass
+
+ def bar():
+ pass
+
+ baz = lambda i : i
+
+ x, y, z = Foo, bar, baz
+
+ if x == Foo:
+ x #@
+ if x != Foo:
+ x #@
+
+ if y == bar:
+ y #@
+ if y != bar:
+ y #@
+
+ if z == baz:
+ z #@
+ if z != baz:
+ z #@
+ """)
+
+ inferred = node1.inferred()
+ assert len(inferred) == 1
+ assert isinstance(inferred[0], nodes.ClassDef)
+ assert inferred[0].name == "Foo"
+
+ inferred = node3.inferred()
+ assert len(inferred) == 1
+ assert isinstance(inferred[0], nodes.FunctionDef)
+ assert inferred[0].name == "bar"
+
+ inferred = node5.inferred()
+ assert len(inferred) == 1
+ assert isinstance(inferred[0], nodes.Lambda)
+
+ for node in (node2, node4, node6):
+ msg = node_info(node)
+ inferred = node.inferred()
+ assert len(inferred) == 1, msg
+ assert inferred[0] is Uninferable, msg
+
+
+def test_equality_uninferable_operand():
+ """Test that equality constraint is satisfied when either operand is uninferable."""
+ node1, node2, node3, node4 = builder.extract_node("""
+ def f1(x):
+ if x == 3:
+ x #@
+
+ if x != 3:
+ x #@
+
+ def f2(y):
+ x = 3
+ if x == y:
+ x #@
+
+ if x != y:
+ x #@
+ """)
+
+ for node in (node1, node2):
+ msg = node_info(node)
+ inferred = node.inferred()
+ assert len(inferred) == 1, msg
+ assert inferred[0] is Uninferable, msg
+
+ for node in (node3, node4):
+ msg = node_info(node)
+ inferred = node.inferred()
+ assert len(inferred) == 1, msg
+ assert isinstance(inferred[0], nodes.Const), msg
+ assert inferred[0].value == 3, msg
+
+
+def test_equality_ambiguous_operand():
+ """Test that equality constraint is satisfied when the compared operand has multiple inferred values."""
+ node1, node2 = builder.extract_node("""
+ def f(y = 1):
+ x = 3
+ if x == y:
+ x #@
+
+ if x != y:
+ x #@
+ """)
+
+ for node in (node1, node2):
+ msg = node_info(node)
+ inferred = node.inferred()
+ assert len(inferred) == 1, msg
+ assert isinstance(inferred[0], nodes.Const), msg
+ assert inferred[0].value == 3, msg
+
+
+def test_equality_fractions():
+ """Test that equality constraint is satisfied when both operands are fractions."""
+ node1, node2, node3, node4 = builder.extract_node("""
+ from fractions import Fraction
+
+ x = Fraction(1, 3)
+ y = Fraction(1, 3)
+
+ if x == y:
+ x #@
+ y #@
+
+ if x != y:
+ x #@
+ y #@
+ """)
+
+ for node in (node1, node2, node3, node4):
+ msg = node_info(node)
+ inferred = node.inferred()
+ assert len(inferred) == 1, msg
+ assert isinstance(inferred[0], Instance), msg
+ assert isinstance(inferred[0]._proxied, nodes.ClassDef), msg
+ assert inferred[0]._proxied.name == "Fraction", msg
diff --git a/tests/test_get_relative_base_path.py b/tests/test_get_relative_base_path.py
new file mode 100644
index 0000000..280a725
--- /dev/null
+++ b/tests/test_get_relative_base_path.py
@@ -0,0 +1,119 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
+import os
+import tempfile
+import unittest
+
+from astroid import modutils
+
+
+class TestModUtilsRelativePath(unittest.TestCase):
+
+ def setUp(self):
+ self.cwd = os.getcwd()
+
+ def _run_relative_path_test(self, target, base, expected):
+ if not (target and base):
+ result = None
+ else:
+ base_dir = os.path.join(self.cwd, base)
+ target_path = os.path.join(self.cwd, target)
+ result = modutils._get_relative_base_path(target_path, base_dir)
+ self.assertEqual(result, expected)
+
+ def test_similar_prefixes_no_match(self):
+
+ cases = [
+ ("something", "some", None),
+ ("some-thing", "some", None),
+ ("some2", "some", None),
+ ("somedir", "some", None),
+ ("some_thing", "some", None),
+ ("some.dir", "some", None),
+ ]
+ for target, base, expected in cases:
+ with self.subTest(target=target, base=base):
+ self._run_relative_path_test(target, base, expected)
+
+ def test_valid_subdirectories(self):
+
+ cases = [
+ ("some/sub", "some", ["sub"]),
+ ("some/foo/bar", "some", ["foo", "bar"]),
+ ("some/foo-bar", "some", ["foo-bar"]),
+ ("some/foo/bar-ext", "some/foo", ["bar-ext"]),
+ ("something/sub", "something", ["sub"]),
+ ]
+ for target, base, expected in cases:
+ with self.subTest(target=target, base=base):
+ self._run_relative_path_test(target, base, expected)
+
+ def test_path_format_variations(self):
+
+ cases = [
+ ("some", "some", []),
+ ("some/", "some", []),
+ ("../some", "some", None),
+ ]
+
+ if os.path.isabs("/abs/path"):
+ cases.append(("/abs/path/some", "/abs/path", ["some"]))
+
+ for target, base, expected in cases:
+ with self.subTest(target=target, base=base):
+ self._run_relative_path_test(target, base, expected)
+
+ def test_case_sensitivity(self):
+
+ cases = [
+ ("Some/sub", "some", None if os.path.sep == "/" else ["sub"]),
+ ("some/Sub", "some", ["Sub"]),
+ ]
+ for target, base, expected in cases:
+ with self.subTest(target=target, base=base):
+ self._run_relative_path_test(target, base, expected)
+
+ def test_special_path_components(self):
+
+ cases = [
+ ("some/.hidden", "some", [".hidden"]),
+ ("some/with space", "some", ["with space"]),
+ ("some/unicode_ø", "some", ["unicode_ø"]),
+ ]
+ for target, base, expected in cases:
+ with self.subTest(target=target, base=base):
+ self._run_relative_path_test(target, base, expected)
+
+ def test_nonexistent_paths(self):
+
+ cases = [("nonexistent", "some", None), ("some/sub", "nonexistent", None)]
+ for target, base, expected in cases:
+ with self.subTest(target=target, base=base):
+ self._run_relative_path_test(target, base, expected)
+
+ def test_empty_paths(self):
+
+ cases = [("", "some", None), ("some", "", None), ("", "", None)]
+ for target, base, expected in cases:
+ with self.subTest(target=target, base=base):
+ self._run_relative_path_test(target, base, expected)
+
+ def test_symlink_resolution(self):
+ with tempfile.TemporaryDirectory() as tmpdir:
+ base_dir = os.path.join(tmpdir, "some")
+ os.makedirs(base_dir, exist_ok=True)
+
+ real_file = os.path.join(base_dir, "real.py")
+ with open(real_file, "w", encoding="utf-8") as f:
+ f.write("# dummy content")
+
+ symlink_path = os.path.join(tmpdir, "symlink.py")
+ os.symlink(real_file, symlink_path)
+
+ result = modutils._get_relative_base_path(symlink_path, base_dir)
+ self.assertEqual(result, ["real"])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/test_group_exceptions.py b/tests/test_group_exceptions.py
index 2ee4143..9680664 100644
--- a/tests/test_group_exceptions.py
+++ b/tests/test_group_exceptions.py
@@ -6,25 +6,30 @@
import pytest
from astroid import (
- AssignName,
- ExceptHandler,
- For,
- Name,
- Try,
Uninferable,
bases,
extract_node,
+ nodes,
)
from astroid.const import PY311_PLUS
from astroid.context import InferenceContext
-from astroid.nodes import Expr, Raise, TryStar
+
+
+@pytest.mark.skipif(not PY311_PLUS, reason="Exception group introduced in Python 3.11")
+def test_group_exceptions_exceptions() -> None:
+ node = extract_node(textwrap.dedent("""
+ try:
+ raise ExceptionGroup('', [TypeError(), TypeError()])
+ except ExceptionGroup as eg:
+ eg.exceptions #@"""))
+
+ inferred = node.inferred()[0]
+ assert isinstance(inferred, nodes.Tuple)
@pytest.mark.skipif(not PY311_PLUS, reason="Requires Python 3.11 or higher")
def test_group_exceptions() -> None:
- node = extract_node(
- textwrap.dedent(
- """
+ node = extract_node(textwrap.dedent("""
try:
raise ExceptionGroup("group", [ValueError(654)])
except ExceptionGroup as eg:
@@ -32,29 +37,26 @@
if isinstance(err, ValueError):
print("Handling ValueError")
elif isinstance(err, TypeError):
- print("Handling TypeError")"""
- )
- )
- assert isinstance(node, Try)
+ print("Handling TypeError")"""))
+ assert isinstance(node, nodes.Try)
handler = node.handlers[0]
assert node.block_range(lineno=1) == (1, 9)
assert node.block_range(lineno=2) == (2, 2)
assert node.block_range(lineno=5) == (5, 9)
- assert isinstance(handler, ExceptHandler)
+ assert isinstance(handler, nodes.ExceptHandler)
assert handler.type.name == "ExceptionGroup"
children = list(handler.get_children())
assert len(children) == 3
exception_group, short_name, for_loop = children
- assert isinstance(exception_group, Name)
+ assert isinstance(exception_group, nodes.Name)
assert exception_group.block_range(1) == (1, 4)
- assert isinstance(short_name, AssignName)
- assert isinstance(for_loop, For)
+ assert isinstance(short_name, nodes.AssignName)
+ assert isinstance(for_loop, nodes.For)
@pytest.mark.skipif(not PY311_PLUS, reason="Requires Python 3.11 or higher")
def test_star_exceptions() -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
try:
raise ExceptionGroup("group", [ValueError(654)])
except* ValueError:
@@ -64,12 +66,11 @@
else:
sys.exit(127)
finally:
- sys.exit(0)"""
- )
+ sys.exit(0)""")
node = extract_node(code)
- assert isinstance(node, TryStar)
+ assert isinstance(node, nodes.TryStar)
assert node.as_string() == code.replace('"', "'").strip()
- assert isinstance(node.body[0], Raise)
+ assert isinstance(node.body[0], nodes.Raise)
assert node.block_range(1) == (1, 11)
assert node.block_range(2) == (2, 2)
assert node.block_range(3) == (3, 3)
@@ -83,28 +84,54 @@
assert node.block_range(11) == (11, 11)
assert node.handlers
handler = node.handlers[0]
- assert isinstance(handler, ExceptHandler)
+ assert isinstance(handler, nodes.ExceptHandler)
assert handler.type.name == "ValueError"
orelse = node.orelse[0]
- assert isinstance(orelse, Expr)
+ assert isinstance(orelse, nodes.Expr)
assert orelse.value.args[0].value == 127
final = node.finalbody[0]
- assert isinstance(final, Expr)
+ assert isinstance(final, nodes.Expr)
assert final.value.args[0].value == 0
@pytest.mark.skipif(not PY311_PLUS, reason="Requires Python 3.11 or higher")
def test_star_exceptions_infer_name() -> None:
- trystar = extract_node(
- """
+ trystar = extract_node("""
try:
1/0
except* ValueError:
- pass"""
- )
+ pass""")
name = "arbitraryName"
context = InferenceContext()
context.lookupname = name
stmts = bases._infer_stmts([trystar], context)
assert list(stmts) == [Uninferable]
assert context.lookupname == name
+
+
+@pytest.mark.skipif(not PY311_PLUS, reason="Requires Python 3.11 or higher")
+def test_star_exceptions_infer_exceptions() -> None:
+ code = textwrap.dedent("""
+ try:
+ raise ExceptionGroup("group", [ValueError(654), TypeError(10)])
+ except* ValueError as ve:
+ print(e.exceptions)
+ except* TypeError as te:
+ print(e.exceptions)
+ else:
+ sys.exit(127)
+ finally:
+ sys.exit(0)""")
+ node = extract_node(code)
+ assert isinstance(node, nodes.TryStar)
+ inferred_ve = next(node.handlers[0].statement().name.infer())
+ assert inferred_ve.name == "ExceptionGroup"
+ assert isinstance(inferred_ve.getattr("exceptions")[0], nodes.List)
+ assert (
+ inferred_ve.getattr("exceptions")[0].elts[0].pytype() == "builtins.ValueError"
+ )
+
+ inferred_te = next(node.handlers[1].statement().name.infer())
+ assert inferred_te.name == "ExceptionGroup"
+ assert isinstance(inferred_te.getattr("exceptions")[0], nodes.List)
+ assert inferred_te.getattr("exceptions")[0].elts[0].pytype() == "builtins.TypeError"
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
index 170176f..c927f6c 100644
--- a/tests/test_helpers.py
+++ b/tests/test_helpers.py
@@ -10,8 +10,8 @@
from astroid import builder, helpers, manager, nodes, raw_building, util
from astroid.builder import AstroidBuilder
from astroid.const import IS_PYPY
-from astroid.exceptions import _NonDeducibleTypeHierarchy
-from astroid.nodes.scoped_nodes import ClassDef
+from astroid.exceptions import InferenceError, _NonDeducibleTypeHierarchy
+from astroid.nodes.node_classes import UNATTACHED_UNKNOWN
class TestHelpers(unittest.TestCase):
@@ -22,14 +22,14 @@
self.builtins = astroid_manager.astroid_cache[builtins_name]
self.manager = manager.AstroidManager()
- def _extract(self, obj_name: str) -> ClassDef:
+ def _extract(self, obj_name: str) -> nodes.ClassDef:
return self.builtins.getattr(obj_name)[0]
- def _build_custom_builtin(self, obj_name: str) -> ClassDef:
+ def _build_custom_builtin(self, obj_name: str) -> nodes.ClassDef:
proxy = raw_building.build_class(obj_name, self.builtins)
return proxy
- def assert_classes_equal(self, cls: ClassDef, other: ClassDef) -> None:
+ def assert_classes_equal(self, cls: nodes.ClassDef, other: nodes.ClassDef) -> None:
self.assertEqual(cls.name, other.name)
self.assertEqual(cls.parent, other.parent)
self.assertEqual(cls.qname(), other.qname())
@@ -55,8 +55,7 @@
self.assert_classes_equal(objtype, expected)
def test_object_type_classes_and_functions(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
def generator():
yield
@@ -76,8 +75,7 @@
A.static_method #@
A().static_method #@
generator() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
from_self = helpers.object_type(ast_nodes[0])
cls = next(ast_nodes[1].infer())
@@ -105,14 +103,12 @@
self.assert_classes_equal(node_type, expected_type)
def test_object_type_metaclasses(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
import abc
class Meta(metaclass=abc.ABCMeta):
pass
meta_instance = Meta()
- """
- )
+ """)
meta_type = helpers.object_type(module["Meta"])
self.assert_classes_equal(meta_type, module["Meta"].metaclass())
@@ -121,8 +117,7 @@
self.assert_classes_equal(instance_type, module["Meta"])
def test_object_type_most_derived(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A(type):
def __new__(*args, **kwargs):
return type.__new__(*args, **kwargs)
@@ -132,8 +127,7 @@
# The most derived metaclass of D is A rather than type.
class D(B , C): #@
pass
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
metaclass = node.metaclass()
self.assertEqual(metaclass.name, "A")
@@ -141,12 +135,10 @@
self.assertEqual(metaclass, obj_type)
def test_inference_errors(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from unknown import Unknown
u = Unknown #@
- """
- )
+ """)
self.assertEqual(helpers.object_type(node), util.Uninferable)
@pytest.mark.skipif(IS_PYPY, reason="__code__ will not be Unknown on PyPy")
@@ -155,8 +147,7 @@
self.assertIs(helpers.object_type(node), util.Uninferable)
def test_object_type_too_many_types(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from unknown import Unknown
def test(x):
if x:
@@ -164,21 +155,18 @@
else:
return 1
test(Unknown) #@
- """
- )
+ """)
self.assertEqual(helpers.object_type(node), util.Uninferable)
def test_is_subtype(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class int_subclass(int):
pass
class A(object): pass #@
class B(A): pass #@
class C(A): pass #@
int_subclass() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
cls_a = ast_nodes[0]
cls_b = ast_nodes[1]
@@ -197,16 +185,14 @@
self.assertFalse(helpers.is_subtype(cls_a, cls_b))
def test_is_subtype_supertype_mro_error(self) -> None:
- cls_e, cls_f = builder.extract_node(
- """
+ cls_e, cls_f = builder.extract_node("""
class A(object): pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E(C, B): pass #@
class F(D, E): pass #@
- """
- )
+ """)
self.assertFalse(helpers.is_subtype(cls_e, cls_f))
self.assertFalse(helpers.is_subtype(cls_e, cls_f))
@@ -215,48 +201,40 @@
self.assertFalse(helpers.is_supertype(cls_f, cls_e))
def test_is_subtype_supertype_unknown_bases(self) -> None:
- cls_a, cls_b = builder.extract_node(
- """
+ cls_a, cls_b = builder.extract_node("""
from unknown import Unknown
class A(Unknown): pass #@
class B(A): pass #@
- """
- )
+ """)
with self.assertRaises(_NonDeducibleTypeHierarchy):
helpers.is_subtype(cls_a, cls_b)
with self.assertRaises(_NonDeducibleTypeHierarchy):
helpers.is_supertype(cls_a, cls_b)
def test_is_subtype_supertype_unrelated_classes(self) -> None:
- cls_a, cls_b = builder.extract_node(
- """
+ cls_a, cls_b = builder.extract_node("""
class A(object): pass #@
class B(object): pass #@
- """
- )
+ """)
self.assertFalse(helpers.is_subtype(cls_a, cls_b))
self.assertFalse(helpers.is_subtype(cls_b, cls_a))
self.assertFalse(helpers.is_supertype(cls_a, cls_b))
self.assertFalse(helpers.is_supertype(cls_b, cls_a))
def test_is_subtype_supertype_classes_no_type_ancestor(self) -> None:
- cls_a = builder.extract_node(
- """
+ cls_a = builder.extract_node("""
class A(object): #@
pass
- """
- )
+ """)
builtin_type = self._extract("type")
self.assertFalse(helpers.is_supertype(builtin_type, cls_a))
self.assertFalse(helpers.is_subtype(cls_a, builtin_type))
def test_is_subtype_supertype_classes_metaclasses(self) -> None:
- cls_a = builder.extract_node(
- """
+ cls_a = builder.extract_node("""
class A(type): #@
pass
- """
- )
+ """)
builtin_type = self._extract("type")
self.assertTrue(helpers.is_supertype(builtin_type, cls_a))
self.assertTrue(helpers.is_subtype(cls_a, builtin_type))
@@ -269,9 +247,72 @@
def test_safe_infer_shim() -> None:
with pytest.warns(DeprecationWarning) as records:
- helpers.safe_infer(nodes.Unknown())
+ helpers.safe_infer(UNATTACHED_UNKNOWN)
assert (
"Import safe_infer from astroid.util; this shim in astroid.helpers will be removed."
in records[0].message.args[0]
)
+
+
+def test_class_to_container() -> None:
+ node = builder.extract_node("""isinstance(3, int)""")
+
+ container = helpers.class_or_tuple_to_container(node.args[1])
+
+ assert len(container) == 1
+ assert isinstance(container[0], nodes.ClassDef)
+ assert container[0].name == "int"
+
+
+def test_tuple_to_container() -> None:
+ node = builder.extract_node("""isinstance(3, (int, str))""")
+
+ container = helpers.class_or_tuple_to_container(node.args[1])
+
+ assert len(container) == 2
+
+ assert isinstance(container[0], nodes.ClassDef)
+ assert container[0].name == "int"
+
+ assert isinstance(container[1], nodes.ClassDef)
+ assert container[1].name == "str"
+
+
+def test_class_to_container_uninferable() -> None:
+ node = builder.extract_node("""
+ def f(x):
+ isinstance(3, x) #@
+ """)
+
+ container = helpers.class_or_tuple_to_container(node.args[1])
+
+ assert len(container) == 1
+ assert container[0] is util.Uninferable
+
+
+def test_tuple_to_container_uninferable() -> None:
+ node = builder.extract_node("""
+ def f(x, y):
+ isinstance(3, (x, y)) #@
+ """)
+
+ container = helpers.class_or_tuple_to_container(node.args[1])
+
+ assert len(container) == 2
+ assert container[0] is util.Uninferable
+ assert container[1] is util.Uninferable
+
+
+def test_class_to_container_inference_error() -> None:
+ node = builder.extract_node("""isinstance(3, undefined_type)""")
+
+ with pytest.raises(InferenceError):
+ helpers.class_or_tuple_to_container(node.args[1])
+
+
+def test_tuple_to_container_inference_error() -> None:
+ node = builder.extract_node("""isinstance(3, (int, undefined_type))""")
+
+ with pytest.raises(InferenceError):
+ helpers.class_or_tuple_to_container(node.args[1])
diff --git a/tests/test_inference.py b/tests/test_inference.py
index 8184167..024ad85 100644
--- a/tests/test_inference.py
+++ b/tests/test_inference.py
@@ -19,9 +19,6 @@
import pytest
from astroid import (
- Assign,
- Const,
- Slice,
Uninferable,
arguments,
manager,
@@ -34,7 +31,7 @@
from astroid.arguments import CallSite
from astroid.bases import BoundMethod, Generator, Instance, UnboundMethod, UnionType
from astroid.builder import AstroidBuilder, _extract_single_node, extract_node, parse
-from astroid.const import IS_PYPY, PY310_PLUS, PY312_PLUS
+from astroid.const import IS_PYPY, PY312_PLUS, PY314_PLUS
from astroid.context import CallContext, InferenceContext
from astroid.exceptions import (
AstroidTypeError,
@@ -71,7 +68,7 @@
raise InferenceError
infer_default = decoratorsmod.path_wrapper(infer_default)
- infer_end = decoratorsmod.path_wrapper(Slice._infer)
+ infer_end = decoratorsmod.path_wrapper(nodes.Slice._infer)
with self.assertRaises(InferenceError):
next(infer_default(1))
self.assertEqual(next(infer_end(1)), 1)
@@ -151,13 +148,11 @@
ast = parse(CODE, __name__)
def test_arg_keyword_no_default_value(self):
- node = extract_node(
- """
+ node = extract_node("""
class Sensor:
def __init__(self, *, description): #@
self._id = description.key
- """
- )
+ """)
with self.assertRaises(NoDefault):
node.args.default_value("description")
@@ -166,8 +161,7 @@
node.args.default_value("name")
def test_infer_abstract_property_return_values(self) -> None:
- module = parse(
- """
+ module = parse("""
import abc
class A(object):
@@ -177,8 +171,7 @@
a = A()
x = a.test
- """
- )
+ """)
inferred = next(module["x"].infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 42)
@@ -665,7 +658,7 @@
inferred = node.inferred()
self.assertEqual(len(inferred), 1)
value_node = inferred[0]
- self.assertIsInstance(value_node, Const)
+ self.assertIsInstance(value_node, nodes.Const)
self.assertEqual(value_node.value, "Hello John!")
def test_float_complex_ambiguity(self) -> None:
@@ -724,8 +717,7 @@
self.assertEqual(inferred.value, expected_value)
def test_invalid_subscripts(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class NoGetitem(object):
pass
class InvalidGetitem(object):
@@ -737,8 +729,7 @@
InvalidGetitem2()[10] #@
[1, 2, 3][None] #@
'lala'['bala'] #@
- """
- )
+ """)
for node in ast_nodes:
self.assertRaises(InferenceError, next, node.infer())
@@ -783,13 +774,11 @@
self.assertEqual(list(sorted(values)), [2, 3])
def test_simple_tuple(self) -> None:
- module = parse(
- """
+ module = parse("""
a = (1,)
b = (22,)
some = a + b #@
- """
- )
+ """)
ast = next(module["some"].infer())
self.assertIsInstance(ast, nodes.Tuple)
self.assertEqual(len(ast.elts), 2)
@@ -993,14 +982,12 @@
reason="pathlib.Path cannot be inferred on Python 3.8",
)
def test_factory_methods_inside_binary_operation(self):
- node = extract_node(
- """
+ node = extract_node("""
from pathlib import Path
h = Path("/home")
u = h / "user"
u #@
- """
- )
+ """)
assert next(node.infer()).qname() == "pathlib.Path"
def test_import_as(self) -> None:
@@ -1057,29 +1044,25 @@
self._test_const_inferred(ast["b"], True)
def test_unary_op_numbers(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
+1 #@
-1 #@
~1 #@
+2.0 #@
-2.0 #@
- """
- )
+ """)
expected = [1, -1, -2, 2.0, -2.0]
for node, expected_value in zip(ast_nodes, expected):
inferred = next(node.infer())
self.assertEqual(inferred.value, expected_value)
def test_matmul(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class Array:
def __matmul__(self, other):
return 42
Array() @ Array() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 42)
@@ -1121,15 +1104,13 @@
self._test_const_inferred(ast["a"], 23 << 1)
def test_binary_op_other_type(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A:
def __add__(self, other):
return other + 42
A() + 1 #@
1 + A() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, nodes.Const)
@@ -1139,15 +1120,13 @@
self.assertEqual(second, util.Uninferable)
def test_binary_op_other_type_using_reflected_operands(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A(object):
def __radd__(self, other):
return other + 42
A() + 1 #@
1 + A() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertEqual(first, util.Uninferable)
@@ -1157,14 +1136,12 @@
self.assertEqual(second.value, 43)
def test_binary_op_reflected_and_not_implemented_is_type_error(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __radd__(self, other): return NotImplemented
1 + A() #@
- """
- )
+ """)
first = next(ast_node.infer())
self.assertEqual(first, util.Uninferable)
@@ -1292,57 +1269,57 @@
tuple | int #@
"""
ast_nodes = extract_node(code)
- if not PY310_PLUS:
- for n in ast_nodes:
- assert n.inferred() == [util.Uninferable]
- else:
- i0 = ast_nodes[0].inferred()[0]
- assert isinstance(i0, UnionType)
- assert isinstance(i0.left, nodes.ClassDef)
- assert i0.left.name == "int"
- assert isinstance(i0.right, nodes.Const)
- assert i0.right.value is None
+ i0 = ast_nodes[0].inferred()[0]
+ assert isinstance(i0, UnionType)
+ assert isinstance(i0.left, nodes.ClassDef)
+ assert i0.left.name == "int"
+ assert isinstance(i0.right, nodes.Const)
+ assert i0.right.value is None
- # Assert basic UnionType properties and methods
- assert i0.callable() is False
- assert i0.bool_value() is True
- assert i0.pytype() == "types.UnionType"
- assert i0.display_type() == "UnionType"
+ # Assert basic UnionType properties and methods
+ assert i0.callable() is False
+ assert i0.bool_value() is True
+ assert i0.pytype() == "types.UnionType"
+ assert i0.display_type() == "UnionType"
+ if PY314_PLUS:
+ assert str(i0) == "UnionType(Union)"
+ assert repr(i0) == f"<UnionType(Union) l.0 at 0x{id(i0)}>"
+ else:
assert str(i0) == "UnionType(UnionType)"
assert repr(i0) == f"<UnionType(UnionType) l.0 at 0x{id(i0)}>"
- i1 = ast_nodes[1].inferred()[0]
- assert isinstance(i1, UnionType)
+ i1 = ast_nodes[1].inferred()[0]
+ assert isinstance(i1, UnionType)
- i2 = ast_nodes[2].inferred()[0]
- assert isinstance(i2, UnionType)
- assert isinstance(i2.left, UnionType)
- assert isinstance(i2.left.left, nodes.ClassDef)
- assert i2.left.left.name == "int"
- assert isinstance(i2.left.right, nodes.ClassDef)
- assert i2.left.right.name == "str"
- assert isinstance(i2.right, nodes.Const)
- assert i2.right.value is None
+ i2 = ast_nodes[2].inferred()[0]
+ assert isinstance(i2, UnionType)
+ assert isinstance(i2.left, UnionType)
+ assert isinstance(i2.left.left, nodes.ClassDef)
+ assert i2.left.left.name == "int"
+ assert isinstance(i2.left.right, nodes.ClassDef)
+ assert i2.left.right.name == "str"
+ assert isinstance(i2.right, nodes.Const)
+ assert i2.right.value is None
- i3 = ast_nodes[3].inferred()[0]
- assert isinstance(i3, UnionType)
- assert isinstance(i3.left, nodes.ClassDef)
- assert i3.left.name == "A"
- assert isinstance(i3.right, nodes.ClassDef)
- assert i3.right.name == "B"
+ i3 = ast_nodes[3].inferred()[0]
+ assert isinstance(i3, UnionType)
+ assert isinstance(i3.left, nodes.ClassDef)
+ assert i3.left.name == "A"
+ assert isinstance(i3.right, nodes.ClassDef)
+ assert i3.right.name == "B"
- i4 = ast_nodes[4].inferred()[0]
- assert isinstance(i4, UnionType)
+ i4 = ast_nodes[4].inferred()[0]
+ assert isinstance(i4, UnionType)
- i5 = ast_nodes[5].inferred()[0]
- assert isinstance(i5, UnionType)
- assert isinstance(i5.left, nodes.ClassDef)
- assert i5.left.name == "List"
+ i5 = ast_nodes[5].inferred()[0]
+ assert isinstance(i5, UnionType)
+ assert isinstance(i5.left, nodes.ClassDef)
+ assert i5.left.name == "List"
- i6 = ast_nodes[6].inferred()[0]
- assert isinstance(i6, UnionType)
- assert isinstance(i6.left, nodes.ClassDef)
- assert i6.left.name == "tuple"
+ i6 = ast_nodes[6].inferred()[0]
+ assert isinstance(i6, UnionType)
+ assert isinstance(i6.left, nodes.ClassDef)
+ assert i6.left.name == "tuple"
code = """
from typing import List
@@ -1355,26 +1332,22 @@
Alias1 | Alias2 #@
"""
ast_nodes = extract_node(code)
- if not PY310_PLUS:
- for n in ast_nodes:
- assert n.inferred() == [util.Uninferable]
- else:
- i0 = ast_nodes[0].inferred()[0]
- assert isinstance(i0, UnionType)
- assert isinstance(i0.left, nodes.ClassDef)
- assert i0.left.name == "List"
+ i0 = ast_nodes[0].inferred()[0]
+ assert isinstance(i0, UnionType)
+ assert isinstance(i0.left, nodes.ClassDef)
+ assert i0.left.name == "List"
- i1 = ast_nodes[1].inferred()[0]
- assert isinstance(i1, UnionType)
- assert isinstance(i1.left, UnionType)
- assert isinstance(i1.left.left, nodes.ClassDef)
- assert i1.left.left.name == "str"
+ i1 = ast_nodes[1].inferred()[0]
+ assert isinstance(i1, UnionType)
+ assert isinstance(i1.left, UnionType)
+ assert isinstance(i1.left.left, nodes.ClassDef)
+ assert i1.left.left.name == "str"
- i2 = ast_nodes[2].inferred()[0]
- assert isinstance(i2, UnionType)
- assert isinstance(i2.left, nodes.ClassDef)
- assert i2.left.name == "List"
- assert isinstance(i2.right, UnionType)
+ i2 = ast_nodes[2].inferred()[0]
+ assert isinstance(i2, UnionType)
+ assert isinstance(i2.left, nodes.ClassDef)
+ assert i2.left.name == "List"
+ assert isinstance(i2.right, UnionType)
def test_nonregr_lambda_arg(self) -> None:
code = """
@@ -1510,7 +1483,7 @@
def test_python25_no_relative_import(self) -> None:
ast = resources.build_file("data/package/absimport.py")
- self.assertTrue(ast.absolute_import_activated(), True)
+ self.assertTrue(ast.absolute_import_activated())
inferred = next(
test_utils.get_name_node(ast, "import_package_subpackage_module").infer()
)
@@ -1519,7 +1492,7 @@
def test_nonregr_absolute_import(self) -> None:
ast = resources.build_file("data/absimp/string.py", "data.absimp.string")
- self.assertTrue(ast.absolute_import_activated(), True)
+ self.assertTrue(ast.absolute_import_activated())
inferred = next(test_utils.get_name_node(ast, "string").infer())
self.assertIsInstance(inferred, nodes.Module)
self.assertEqual(inferred.name, "string")
@@ -1679,12 +1652,10 @@
self.assertEqual(len(inferred), 1, inferred)
def test__new__bound_methods(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class cls(object): pass
cls().__new__(cls) #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred._proxied, node.root()["cls"])
@@ -1884,12 +1855,11 @@
self.assertEqual(node.type, "function")
@pytest.mark.skipif(
- IS_PYPY and PY310_PLUS,
+ IS_PYPY,
reason="Persistent recursion error that we ignore and never fix",
)
def test_no_infinite_ancestor_loop(self) -> None:
- klass = extract_node(
- """
+ klass = extract_node("""
import datetime
def method(self):
@@ -1897,8 +1867,7 @@
class something(datetime.datetime): #@
pass
- """
- )
+ """)
ancestors = [base.name for base in klass.ancestors()]
expected_subset = ["datetime", "date"]
self.assertEqual(expected_subset, ancestors[:2])
@@ -2029,8 +1998,7 @@
)
def test_starred_in_mapping_literal_no_inference_possible(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
from unknown import unknown
def test(a):
@@ -2041,8 +2009,7 @@
return {0: 1, **a}
test(**func())
- """
- )
+ """)
self.assertEqual(next(node.infer()), util.Uninferable)
def test_starred_in_mapping_inference_issues(self) -> None:
@@ -2161,15 +2128,13 @@
self.assertEqual(inferred.qname(), "builtins.list")
def test_conversion_of_dict_methods(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
list({1:2, 2:3}.values()) #@
list({1:2, 2:3}.keys()) #@
tuple({1:2, 2:3}.values()) #@
tuple({1:2, 3:4}.keys()) #@
set({1:2, 2:4}.keys()) #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
self.assertInferList(ast_nodes[0], [2, 3])
self.assertInferList(ast_nodes[1], [1, 2])
@@ -2381,8 +2346,7 @@
self.assertEqual(inferred.qname(), "collections.Counter")
def test_inferring_with_statement_failures(self) -> None:
- module = parse(
- """
+ module = parse("""
class NoEnter(object):
pass
class NoMethod(object):
@@ -2397,15 +2361,13 @@
pass
with NoElts() as (no_elts, no_elts1):
pass
- """
- )
+ """)
self.assertRaises(InferenceError, next, module["no_enter"].infer())
self.assertRaises(InferenceError, next, module["no_method"].infer())
self.assertRaises(InferenceError, next, module["no_elts"].infer())
def test_inferring_with_statement(self) -> None:
- module = parse(
- """
+ module = parse("""
class SelfContext(object):
def __enter__(self):
return self
@@ -2430,8 +2392,7 @@
pass
with MultipleReturns2() as (stdout, (stderr, stdin)):
pass
- """
- )
+ """)
self_context = module["self_context"]
inferred = next(self_context.infer())
self.assertIsInstance(inferred, Instance)
@@ -2457,8 +2418,7 @@
self.assertEqual(inferred.value, 2)
def test_inferring_with_contextlib_contextmanager(self) -> None:
- module = parse(
- """
+ module = parse("""
import contextlib
from contextlib import contextmanager
@@ -2489,8 +2449,7 @@
pass
with manager_multiple() as (first, second):
pass
- """
- )
+ """)
none = module["none"]
inferred = next(none.infer())
self.assertIsInstance(inferred, nodes.Const)
@@ -2517,21 +2476,18 @@
# indices. This is the case of contextlib.nested, where the
# result is a list, which is mutated later on, so it's
# undetected by astroid.
- module = parse(
- """
+ module = parse("""
class Manager(object):
def __enter__(self):
return []
with Manager() as (a, b, c):
pass
- """
- )
+ """)
self.assertRaises(InferenceError, next, module["a"].infer())
def test_inferring_context_manager_unpacking_inference_error(self) -> None:
# https://github.com/pylint-dev/pylint/issues/1463
- module = parse(
- """
+ module = parse("""
import contextlib
@contextlib.contextmanager
@@ -2542,13 +2498,11 @@
result = _select_source()
with result as (a, b, c):
pass
- """
- )
+ """)
self.assertRaises(InferenceError, next, module["a"].infer())
def test_inferring_with_contextlib_contextmanager_failures(self) -> None:
- module = parse(
- """
+ module = parse("""
from contextlib import contextmanager
def no_decorators_mgr():
@@ -2566,8 +2520,7 @@
pass
with no_yield_mgr() as no_yield:
pass
- """
- )
+ """)
self.assertRaises(InferenceError, next, module["no_decorators"].infer())
self.assertRaises(InferenceError, next, module["other_decorators"].infer())
self.assertRaises(InferenceError, next, module["no_yield"].infer())
@@ -2607,8 +2560,7 @@
self.assertEqual(util.Uninferable, next(node.infer()))
def test_unary_operands(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
import os
def func(): pass
from missing import missing
@@ -2661,8 +2613,7 @@
~os #@
-func #@
+BadInstance #@
- """
- )
+ """)
expected = [42, 1, 42, -1, 24, 25, 42, 1, 43]
for node, value in zip(ast_nodes[:9], expected):
inferred = next(node.infer())
@@ -2674,18 +2625,15 @@
self.assertEqual(inferred, util.Uninferable)
def test_unary_op_instance_method_not_callable(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A:
__pos__ = (i for i in range(10))
+A() #@
- """
- )
+ """)
self.assertRaises(InferenceError, next, ast_node.infer())
def test_binary_op_type_errors(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
import collections
1 + "a" #@
1 - [] #@
@@ -2727,8 +2675,7 @@
f+=A() #@
x = 1
x+=[] #@
- """
- )
+ """)
msg = "unsupported operand type(s) for {op}: {lhs!r} and {rhs!r}"
expected = [
msg.format(op="+", lhs="int", rhs="str"),
@@ -2769,8 +2716,7 @@
self.assertEqual(errors, [])
def test_unary_type_errors(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
import collections
~[] #@
~() #@
@@ -2787,8 +2733,7 @@
~A() #@
~collections #@
~2.0 #@
- """
- )
+ """)
msg = "bad operand type for unary {op}: {type}"
expected = [
msg.format(op="~", type="list"),
@@ -2814,12 +2759,10 @@
def test_unary_empty_type_errors(self) -> None:
# These aren't supported right now
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
~(2 and []) #@
-(0 or {}) #@
- """
- )
+ """)
expected = [
"bad operand type for unary ~: list",
"bad operand type for unary -: dict",
@@ -2865,19 +2808,16 @@
self.assertTrue(node.bool_value())
def test_name_bool_value(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
x = 42
y = x
y
- """
- )
+ """)
self.assertIs(node.bool_value(), util.Uninferable)
def test_bool_value(self) -> None:
# Verify the truth value of nodes.
- module = parse(
- """
+ module = parse("""
import collections
collections_module = collections
def function(): pass
@@ -2902,8 +2842,7 @@
compare = 2 < 3
const_str_true = 'testconst'
const_str_false = ''
- """
- )
+ """)
collections_module = next(module["collections_module"].infer())
self.assertTrue(collections_module.bool_value())
function = module["function"]
@@ -2936,8 +2875,7 @@
self.assertEqual(compare.bool_value(), util.Uninferable)
def test_bool_value_instances(self) -> None:
- instances = extract_node(
- """
+ instances = extract_node("""
class FalseBoolInstance(object):
def __bool__(self):
return False
@@ -2966,16 +2904,14 @@
TrueLenInstance() #@
AlwaysTrueInstance() #@
ErrorInstance() #@
- """
- )
+ """)
expected = (False, True, False, True, True, util.Uninferable, util.Uninferable)
for node, expected_value in zip(instances, expected):
inferred = next(node.infer())
self.assertEqual(inferred.bool_value(), expected_value)
def test_bool_value_variable(self) -> None:
- instance = extract_node(
- """
+ instance = extract_node("""
class VariableBoolInstance(object):
def __init__(self, value):
self.value = value
@@ -2983,14 +2919,20 @@
return self.value
not VariableBoolInstance(True)
- """
- )
+ """)
inferred = next(instance.infer())
self.assertIs(inferred.bool_value(), util.Uninferable)
+ def test_bool_value_not_implemented(self) -> None:
+ node = extract_node("""NotImplemented""")
+ inferred = next(node.infer())
+ if PY314_PLUS:
+ self.assertIs(inferred.bool_value(), util.Uninferable)
+ else:
+ self.assertIs(inferred.bool_value(), True)
+
def test_infer_coercion_rules_for_floats_complex(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
1 + 1.0 #@
1 * 1.0 #@
2 - 1.0 #@
@@ -2999,20 +2941,17 @@
2 * 1j #@
2 - 1j #@
3 / 1j #@
- """
- )
+ """)
expected_values = [2.0, 1.0, 1.0, 1.0, 1 + 1j, 2j, 2 - 1j, -3j]
for node, expected in zip(ast_nodes, expected_values):
inferred = next(node.infer())
self.assertEqual(inferred.value, expected)
def test_binop_list_with_elts(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
x = [A] * 1
[1] + x
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.List)
self.assertEqual(len(inferred.elts), 2)
@@ -3020,8 +2959,7 @@
self.assertIsInstance(inferred.elts[1], nodes.Unknown)
def test_binop_same_types(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A(object):
def __add__(self, other):
return 42
@@ -3029,8 +2967,7 @@
1 - 1 #@
"a" + "b" #@
A() + A() #@
- """
- )
+ """)
expected_values = [2, 0, "ab", 42]
for node, expected in zip(ast_nodes, expected_values):
inferred = next(node.infer())
@@ -3038,23 +2975,20 @@
self.assertEqual(inferred.value, expected)
def test_binop_different_types_reflected_only(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A(object):
pass
class B(object):
def __radd__(self, other):
return other
A() + B() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "A")
def test_binop_different_types_unknown_bases(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
from foo import bar
class A(bar):
@@ -3063,14 +2997,12 @@
def __radd__(self, other):
return other
A() + B() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIs(inferred, util.Uninferable)
def test_binop_different_types_normal_not_implemented_and_reflected(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A(object):
def __add__(self, other):
return NotImplemented
@@ -3078,94 +3010,80 @@
def __radd__(self, other):
return other
A() + B() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "A")
def test_binop_different_types_no_method_implemented(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A(object):
pass
class B(object): pass
A() + B() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)
def test_binop_different_types_reflected_and_normal_not_implemented(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A(object):
def __add__(self, other): return NotImplemented
class B(object):
def __radd__(self, other): return NotImplemented
A() + B() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)
def test_binop_subtype(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A(object): pass
class B(A):
def __add__(self, other): return other
B() + A() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "A")
def test_binop_subtype_implemented_in_parent(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A(object):
def __add__(self, other): return other
class B(A): pass
B() + A() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "A")
def test_binop_subtype_not_implemented(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A(object):
pass
class B(A):
def __add__(self, other): return NotImplemented
B() + A() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)
def test_binop_supertype(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A(object):
pass
class B(A):
def __radd__(self, other):
return other
A() + B() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "A")
def test_binop_supertype_rop_not_implemented(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A(object):
def __add__(self, other):
return other
@@ -3173,29 +3091,25 @@
def __radd__(self, other):
return NotImplemented
A() + B() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "B")
def test_binop_supertype_both_not_implemented(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A(object):
def __add__(self): return NotImplemented
class B(A):
def __radd__(self, other):
return NotImplemented
A() + B() #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)
def test_binop_inference_errors(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
from unknown import Unknown
class A(object):
def __add__(self, other): return NotImplemented
@@ -3205,14 +3119,12 @@
Unknown + A() #@
B() + A() #@
A() + B() #@
- """
- )
+ """)
for node in ast_nodes:
self.assertEqual(next(node.infer()), util.Uninferable)
def test_binop_ambiguity(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A(object):
def __add__(self, other):
if isinstance(other, B):
@@ -3230,8 +3142,7 @@
B() + A() #@
A() + C() #@
C() + A() #@
- """
- )
+ """)
for node in ast_nodes:
self.assertEqual(next(node.infer()), util.Uninferable)
@@ -3240,8 +3151,7 @@
Reported in https://github.com/pylint-dev/pylint/issues/4826.
"""
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A:
def __init__(self):
for a in [self] + []:
@@ -3251,8 +3161,7 @@
def __init__(self):
for b in [] + [self]:
print(b) #@
- """
- )
+ """)
inferred_a = list(ast_nodes[0].args[0].infer())
self.assertEqual(len(inferred_a), 1)
self.assertIsInstance(inferred_a[0], Instance)
@@ -3264,8 +3173,7 @@
self.assertEqual(inferred_b[0]._proxied.name, "B")
def test_metaclass__getitem__(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class Meta(type):
def __getitem__(cls, arg):
return 24
@@ -3273,16 +3181,14 @@
pass
A['Awesome'] #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 24)
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
def test_with_metaclass__getitem__(self):
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class Meta(type):
def __getitem__(cls, arg):
return 24
@@ -3291,15 +3197,13 @@
pass
A['Awesome'] #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 24)
def test_bin_op_classes(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class Meta(type):
def __or__(self, other):
return 24
@@ -3307,16 +3211,14 @@
pass
A | A
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 24)
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
def test_bin_op_classes_with_metaclass(self):
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class Meta(type):
def __or__(self, other):
return 24
@@ -3325,15 +3227,13 @@
pass
A | A
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 24)
def test_bin_op_supertype_more_complicated_example(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __init__(self):
self.foo = 42
@@ -3347,53 +3247,45 @@
return NotImplemented
A() + B() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(int(inferred.value), 45)
def test_aug_op_same_type_not_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return NotImplemented
def __add__(self, other): return NotImplemented
A() + A() #@
- """
- )
+ """)
self.assertEqual(next(ast_node.infer()), util.Uninferable)
def test_aug_op_same_type_aug_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return other
f = A()
f += A() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "A")
def test_aug_op_same_type_aug_not_implemented_normal_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return NotImplemented
def __add__(self, other): return 42
f = A()
f += A() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 42)
def test_aug_op_subtype_both_not_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return NotImplemented
def __add__(self, other): return NotImplemented
@@ -3401,83 +3293,71 @@
pass
b = B()
b+=A() #@
- """
- )
+ """)
self.assertEqual(next(ast_node.infer()), util.Uninferable)
def test_aug_op_subtype_aug_op_is_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return 42
class B(A):
pass
b = B()
b+=A() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 42)
def test_aug_op_subtype_normal_op_is_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __add__(self, other): return 42
class B(A):
pass
b = B()
b+=A() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 42)
def test_aug_different_types_no_method_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object): pass
class B(object): pass
f = A()
f += B() #@
- """
- )
+ """)
self.assertEqual(next(ast_node.infer()), util.Uninferable)
def test_aug_different_types_augop_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return other
class B(object): pass
f = A()
f += B() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "B")
def test_aug_different_types_aug_not_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return NotImplemented
def __add__(self, other): return other
class B(object): pass
f = A()
f += B() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "B")
def test_aug_different_types_aug_not_implemented_rop_fallback(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return NotImplemented
def __add__(self, other): return NotImplemented
@@ -3485,26 +3365,22 @@
def __radd__(self, other): return other
f = A()
f += B() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "A")
def test_augop_supertypes_none_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object): pass
class B(object): pass
a = A()
a += B() #@
- """
- )
+ """)
self.assertEqual(next(ast_node.infer()), util.Uninferable)
def test_augop_supertypes_not_implemented_returned_for_all(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return NotImplemented
def __add__(self, other): return NotImplemented
@@ -3512,42 +3388,36 @@
def __add__(self, other): return NotImplemented
a = A()
a += B() #@
- """
- )
+ """)
self.assertEqual(next(ast_node.infer()), util.Uninferable)
def test_augop_supertypes_augop_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return other
class B(A): pass
a = A()
a += B() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "B")
def test_augop_supertypes_reflected_binop_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return NotImplemented
class B(A):
def __radd__(self, other): return other
a = A()
a += B() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "A")
def test_augop_supertypes_normal_binop_implemented(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __iadd__(self, other): return NotImplemented
def __add__(self, other): return other
@@ -3556,8 +3426,7 @@
a = A()
a += B() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "B")
@@ -3572,12 +3441,10 @@
self.assertEqual(errors, [])
def test_string_interpolation(self):
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
"a%d%d" % (1, 2) #@
"a%(x)s" % {"x": 42} #@
- """
- )
+ """)
expected = ["a12", "a42"]
for node, expected_value in zip(ast_nodes, expected):
inferred = next(node.infer())
@@ -3585,8 +3452,7 @@
self.assertEqual(inferred.value, expected_value)
def test_mul_list_supports__index__(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class Index(object):
def __index__(self): return 2
class NotIndex(object): pass
@@ -3596,8 +3462,7 @@
a * Index() #@
a * NotIndex() #@
a * NotIndex2() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, nodes.List)
@@ -3607,8 +3472,7 @@
self.assertEqual(inferred, util.Uninferable)
def test_subscript_supports__index__(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class Index(object):
def __index__(self): return 2
class LambdaIndex(object):
@@ -3621,8 +3485,7 @@
a[Index()] #@
a[LambdaIndex()] #@
a[NonIndex()] #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, nodes.Const)
@@ -3633,8 +3496,7 @@
self.assertRaises(InferenceError, next, ast_nodes[2].infer())
def test_special_method_masquerading_as_another(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class Info(object):
def __add__(self, other):
return "lala"
@@ -3642,46 +3504,40 @@
f = Info()
f | Info() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, "lala")
def test_unary_op_assignment(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object): pass
def pos(self):
return 42
A.__pos__ = pos
f = A()
+f #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 42)
def test_unary_op_classes(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class Meta(type):
def __invert__(self):
return 42
class A(object, metaclass=Meta):
pass
~A
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 42)
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
def test_unary_op_classes_with_metaclass(self):
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
import six
class Meta(type):
def __invert__(self):
@@ -3689,8 +3545,7 @@
class A(six.with_metaclass(Meta)):
pass
~A
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 42)
@@ -3786,16 +3641,14 @@
self.assertRaises(InferenceError, next, node.infer())
def test_instance_slicing(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A(object):
def __getitem__(self, index):
return [1, 2, 3, 4, 5][index]
A()[1:] #@
A()[:2] #@
A()[1:4] #@
- """
- )
+ """)
expected_values = [[2, 3, 4, 5], [1, 2], [2, 3, 4]]
for expected, node in zip(expected_values, ast_nodes):
inferred = next(node.infer())
@@ -3803,42 +3656,36 @@
self.assertEqual([elt.value for elt in inferred.elts], expected)
def test_instance_slicing_slices(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
def __getitem__(self, index):
return index
A()[1:] #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Slice)
self.assertEqual(inferred.lower.value, 1)
self.assertIsNone(inferred.upper)
def test_instance_slicing_fails(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A(object):
def __getitem__(self, index):
return 1[index]
A()[4:5] #@
A()[2:] #@
- """
- )
+ """)
for node in ast_nodes:
self.assertEqual(next(node.infer()), util.Uninferable)
def test_type__new__with_metaclass(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class Metaclass(type):
pass
class Entity(object):
pass
type.__new__(Metaclass, 'NewClass', (Entity,), {'a': 1}) #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
@@ -3854,90 +3701,76 @@
self.assertEqual(attributes[0].value, 1)
def test_type__new__not_enough_arguments(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
type.__new__(type, 'foo') #@
type.__new__(type, 'foo', ()) #@
type.__new__(type, 'foo', (), {}, ()) #@
- """
- )
+ """)
for node in ast_nodes:
with pytest.raises(InferenceError):
next(node.infer())
def test_type__new__invalid_mcs_argument(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class Class(object): pass
type.__new__(1, 2, 3, 4) #@
type.__new__(Class, 2, 3, 4) #@
- """
- )
+ """)
for node in ast_nodes:
with pytest.raises(InferenceError):
next(node.infer())
def test_type__new__invalid_name(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class Class(type): pass
type.__new__(Class, object, 1, 2) #@
type.__new__(Class, 1, 1, 2) #@
type.__new__(Class, [], 1, 2) #@
- """
- )
+ """)
for node in ast_nodes:
with pytest.raises(InferenceError):
next(node.infer())
def test_type__new__invalid_bases(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
type.__new__(type, 'a', 1, 2) #@
type.__new__(type, 'a', [], 2) #@
type.__new__(type, 'a', {}, 2) #@
type.__new__(type, 'a', (1, ), 2) #@
type.__new__(type, 'a', (object, 1), 2) #@
- """
- )
+ """)
for node in ast_nodes:
with pytest.raises(InferenceError):
next(node.infer())
def test_type__new__invalid_attrs(self) -> None:
- type_error_nodes = extract_node(
- """
+ type_error_nodes = extract_node("""
type.__new__(type, 'a', (), ()) #@
type.__new__(type, 'a', (), object) #@
type.__new__(type, 'a', (), 1) #@
- """
- )
+ """)
for node in type_error_nodes:
with pytest.raises(InferenceError):
next(node.infer())
# Ignore invalid keys
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
type.__new__(type, 'a', (), {object: 1}) #@
type.__new__(type, 'a', (), {1:2, "a":5}) #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
def test_type__new__metaclass_lookup(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class Metaclass(type):
def test(cls): pass
@classmethod
def test1(cls): pass
attr = 42
type.__new__(Metaclass, 'A', (), {}) #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
test = inferred.getattr("test")
@@ -3956,15 +3789,13 @@
self.assertEqual(attr[0].value, 42)
def test_type__new__metaclass_and_ancestors_lookup(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class Book(object):
title = 'Ubik'
class MetaBook(type):
title = 'Grimus'
type.__new__(MetaBook, 'book', (Book, ), {'title':'Catch 22'}) #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
titles = [
@@ -3987,12 +3818,10 @@
assert not isinstance(inferred2, nodes.Const)
assert inferred2._proxied is inferred._proxied
- ast_node3 = extract_node(
- """
+ ast_node3 = extract_node("""
x = 43
int.__new__(int, x) #@
- """
- )
+ """)
inferred3 = next(ast_node3.infer())
assert isinstance(inferred3, nodes.Const)
assert inferred3.value == 43
@@ -4001,17 +3830,14 @@
with pytest.raises(InferenceError):
next(ast_node4.infer())
- ast_node5 = extract_node(
- """
+ ast_node5 = extract_node("""
class A: pass
A.__new__(A()) #@
- """
- )
+ """)
with pytest.raises(InferenceError):
next(ast_node5.infer())
- ast_nodes6 = extract_node(
- """
+ ast_nodes6 = extract_node("""
class A: pass
class B(A): pass
class C: pass
@@ -4020,8 +3846,7 @@
B.__new__(A) #@
B.__new__(B) #@
C.__new__(A) #@
- """
- )
+ """)
instance_A1 = next(ast_nodes6[0].infer())
assert instance_A1._proxied.name == "A"
instance_B1 = next(ast_nodes6[1].infer())
@@ -4033,8 +3858,7 @@
instance_A3 = next(ast_nodes6[4].infer())
assert instance_A3._proxied.name == "A"
- ast_nodes7 = extract_node(
- """
+ ast_nodes7 = extract_node("""
import enum
class A(enum.EnumMeta): pass
class B(enum.EnumMeta):
@@ -4045,8 +3869,7 @@
return super().__new__(A, "str", (enum.Enum,), enum._EnumDict(), **kwargs)
B("") #@
C() #@
- """
- )
+ """)
instance_B = next(ast_nodes7[0].infer())
assert instance_B._proxied.name == "B"
instance_C = next(ast_nodes7[1].infer())
@@ -4058,8 +3881,7 @@
def test_function_metaclasses(self):
# These are not supported right now, although
# they will be in the future.
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class BookMeta(type):
author = 'Rushdie'
@@ -4069,8 +3891,7 @@
class Book(object, metaclass=metaclass_function):
pass
Book #@
- """
- )
+ """)
inferred = next(ast_node.infer())
metaclass = inferred.metaclass()
self.assertIsInstance(metaclass, nodes.ClassDef)
@@ -4081,8 +3902,7 @@
def test_subscript_inference_error(self) -> None:
# Used to raise StopIteration
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class AttributeDict(dict):
def __getitem__(self, name):
return self
@@ -4090,13 +3910,11 @@
flow['app'] = AttributeDict()
flow['app']['config'] = AttributeDict()
flow['app']['config']['doffing'] = AttributeDict() #@
- """
- )
+ """)
self.assertIsInstance(util.safe_infer(ast_node.targets[0]), Instance)
def test_classmethod_inferred_by_context(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class Super(object):
def instance(cls):
return cls()
@@ -4109,15 +3927,13 @@
# should see the Sub.instance() is returning a Sub
# instance, not a Super instance
Sub.instance().method() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, Instance)
self.assertEqual(inferred.name, "Sub")
def test_infer_call_result_invalid_dunder_call_on_instance(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A:
__call__ = 42
class B:
@@ -4127,20 +3943,17 @@
A() #@
B() #@
C() #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertRaises(InferenceError, next, inferred.infer_call_result(node))
def test_infer_call_result_same_proxied_class(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class A:
__call__ = A()
A() #@
- """
- )
+ """)
inferred = next(node.infer())
fully_evaluated_inference_results = list(inferred.infer_call_result(node))
assert fully_evaluated_inference_results[0].name == "A"
@@ -4151,8 +3964,7 @@
self.assertIsInstance(inferred, nodes.Const)
def test_context_call_for_context_managers(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A:
def __enter__(self):
return self
@@ -4169,8 +3981,7 @@
b #@
with C() as c:
c #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first_a = next(ast_nodes[0].infer())
self.assertIsInstance(first_a, Instance)
@@ -4183,8 +3994,7 @@
self.assertEqual(third_c.name, "A")
def test_metaclass_subclasses_arguments_are_classes_not_instances(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(type):
def test(cls):
return cls
@@ -4192,16 +4002,14 @@
pass
B.test() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
self.assertEqual(inferred.name, "B")
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
def test_with_metaclass_subclasses_arguments_are_classes_not_instances(self):
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(type):
def test(cls):
return cls
@@ -4210,16 +4018,14 @@
pass
B.test() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
self.assertEqual(inferred.name, "B")
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
def test_with_metaclass_with_partial_imported_name(self):
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(type):
def test(cls):
return cls
@@ -4228,23 +4034,20 @@
pass
B.test() #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
self.assertEqual(inferred.name, "B")
def test_infer_cls_in_class_methods(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A(type):
def __call__(cls):
cls #@
class B(object):
def __call__(cls):
cls #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, nodes.ClassDef)
@@ -4253,29 +4056,25 @@
@pytest.mark.xfail(reason="Metaclass arguments not inferred as classes")
def test_metaclass_arguments_are_classes_not_instances(self):
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(type):
def test(cls): return cls
A.test() #@
- """
- )
+ """)
# This is not supported yet
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
self.assertEqual(inferred.name, "A")
def test_metaclass_with_keyword_args(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class TestMetaKlass(type):
def __new__(mcs, name, bases, ns, kwo_arg):
return super().__new__(mcs, name, bases, ns)
class TestKlass(metaclass=TestMetaKlass, kwo_arg=42): #@
pass
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
@@ -4285,9 +4084,7 @@
See https://github.com/pylint-dev/pylint/issues/2159
"""
- val = (
- extract_node(
- """
+ val = extract_node("""
class _Meta(type):
def __call__(cls):
return 1
@@ -4296,45 +4093,36 @@
return 5.5
Clazz() #@
- """
- )
- .inferred()[0]
- .value
- )
+ """).inferred()[0].value
assert val == 1
def test_metaclass_custom_dunder_call_boundnode(self) -> None:
"""The boundnode should be the calling class."""
- cls = extract_node(
- """
+ cls = extract_node("""
class _Meta(type):
def __call__(cls):
return cls
class Clazz(metaclass=_Meta):
pass
Clazz() #@
- """
- ).inferred()[0]
+ """).inferred()[0]
assert isinstance(cls, Instance) and cls.name == "Clazz"
def test_infer_subclass_attr_outer_class(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class Outer:
data = 123
class Test(Outer):
pass
Test.data
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value == 123
def test_infer_subclass_attr_inner_class_works_indirectly(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class Outer:
class Inner:
data = 123
@@ -4343,15 +4131,13 @@
class Test(Inner):
pass
Test.data
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value == 123
def test_infer_subclass_attr_inner_class(self) -> None:
- clsdef_node, attr_node = extract_node(
- """
+ clsdef_node, attr_node = extract_node("""
class Outer:
class Inner:
data = 123
@@ -4360,8 +4146,7 @@
pass
Test #@
Test.data #@
- """
- )
+ """)
clsdef = next(clsdef_node.infer())
assert isinstance(clsdef, nodes.ClassDef)
inferred = next(clsdef.igetattr("data"))
@@ -4378,22 +4163,19 @@
def test_infer_method_empty_body(self) -> None:
# https://github.com/PyCQA/astroid/issues/1015
- node = extract_node(
- """
+ node = extract_node("""
class A:
def foo(self): ...
A().foo() #@
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value is None
def test_infer_method_overload(self) -> None:
# https://github.com/PyCQA/astroid/issues/1015
- node = extract_node(
- """
+ node = extract_node("""
class A:
def foo(self): ...
@@ -4401,15 +4183,13 @@
yield
A().foo() #@
- """
- )
+ """)
inferred = list(node.infer())
assert len(inferred) == 1
assert isinstance(inferred[0], Generator)
def test_infer_function_under_if(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
if 1 in [1]:
def func():
return 42
@@ -4418,14 +4198,12 @@
return False
func() #@
- """
- )
+ """)
inferred = list(node.inferred())
assert [const.value for const in inferred] == [42, False]
def test_infer_property_setter(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class PropertyWithSetter:
@property
def host(self):
@@ -4436,43 +4214,37 @@
self._host = value
PropertyWithSetter().host #@
- """
- )
+ """)
assert not isinstance(next(node.infer()), Instance)
def test_delayed_attributes_without_slots(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class A(object):
__slots__ = ('a', )
a = A()
a.teta = 24
a.a = 24
a #@
- """
- )
+ """)
inferred = next(ast_node.infer())
with self.assertRaises(NotFoundError):
inferred.getattr("teta")
inferred.getattr("a")
def test_lambda_as_methods(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class X:
m = lambda self, arg: self.z + arg
z = 24
X().m(4) #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 28)
def test_inner_value_redefined_by_subclass(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class X(object):
M = lambda self, arg: "a"
x = 24
@@ -4484,15 +4256,13 @@
M = lambda self, arg: arg + 1
def blurb(self):
self.m #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 25)
def test_inner_value_redefined_by_subclass_with_mro(self) -> None:
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
class X(object):
M = lambda self, arg: arg + 1
x = 24
@@ -4507,8 +4277,7 @@
M = lambda self, arg: arg + 1
def blurb(self):
self.m #@
- """
- )
+ """)
inferred = next(ast_node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value == 26
@@ -4516,80 +4285,67 @@
def test_getitem_of_class_raised_type_error(self) -> None:
# Test that we wrap an AttributeInferenceError
# and reraise it as a TypeError in Class.getitem
- node = extract_node(
- """
+ node = extract_node("""
def test(): ...
test()
- """
- )
+ """)
inferred = next(node.infer())
with self.assertRaises(AstroidTypeError):
inferred.getitem(nodes.Const("4"))
def test_infer_arg_called_type_is_uninferable(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
def func(type):
type #@
- """
- )
+ """)
inferred = next(node.infer())
assert inferred is util.Uninferable
def test_infer_arg_called_object_when_used_as_index_is_uninferable(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
def func(object):
['list'][
object #@
]
- """
- )
+ """)
inferred = next(node.infer())
assert inferred is util.Uninferable
def test_infer_arg_called_type_when_used_as_index_is_uninferable(self):
# https://github.com/pylint-dev/astroid/pull/958
- node = extract_node(
- """
+ node = extract_node("""
def func(type):
['list'][
type #@
]
- """
- )
+ """)
inferred = next(node.infer())
assert not isinstance(inferred, nodes.ClassDef) # was inferred as builtins.type
assert inferred is util.Uninferable
def test_infer_arg_called_type_when_used_as_subscript_is_uninferable(self):
# https://github.com/pylint-dev/astroid/pull/958
- node = extract_node(
- """
+ node = extract_node("""
def func(type):
type[0] #@
- """
- )
+ """)
inferred = next(node.infer())
assert not isinstance(inferred, nodes.ClassDef) # was inferred as builtins.type
assert inferred is util.Uninferable
def test_infer_arg_called_type_defined_in_outer_scope_is_uninferable(self):
# https://github.com/pylint-dev/astroid/pull/958
- node = extract_node(
- """
+ node = extract_node("""
def outer(type):
def inner():
type[0] #@
- """
- )
+ """)
inferred = next(node.infer())
assert not isinstance(inferred, nodes.ClassDef) # was inferred as builtins.type
assert inferred is util.Uninferable
def test_infer_subclass_attr_instance_attr_indirect(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class Parent:
def __init__(self):
self.data = 123
@@ -4598,8 +4354,7 @@
pass
t = Test()
t
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, Instance)
const = next(inferred.igetattr("data"))
@@ -4607,8 +4362,7 @@
assert const.value == 123
def test_infer_subclass_attr_instance_attr(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class Parent:
def __init__(self):
self.data = 123
@@ -4617,8 +4371,7 @@
pass
t = Test()
t.data
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value == 123
@@ -4631,8 +4384,7 @@
class GetattrTest(unittest.TestCase):
def test_yes_when_unknown(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
from missing import Missing
getattr(1, Unknown) #@
getattr(Unknown, 'a') #@
@@ -4643,8 +4395,7 @@
getattr(Missing, Missing) #@
getattr('a', Missing) #@
getattr('a', Missing, Missing) #@
- """
- )
+ """)
for node in ast_nodes[:4]:
self.assertRaises(InferenceError, next, node.infer())
@@ -4653,36 +4404,30 @@
self.assertEqual(inferred, util.Uninferable, node)
def test_attrname_not_string(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
getattr(1, 1) #@
c = int
getattr(1, c) #@
- """
- )
+ """)
for node in ast_nodes:
self.assertRaises(InferenceError, next, node.infer())
def test_attribute_missing(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
getattr(1, 'ala') #@
getattr(int, 'ala') #@
getattr(float, 'bala') #@
getattr({}, 'portocala') #@
- """
- )
+ """)
for node in ast_nodes:
self.assertRaises(InferenceError, next, node.infer())
def test_default(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
getattr(1, 'ala', None) #@
getattr(int, 'bala', int) #@
getattr(int, 'bala', getattr(int, 'portocala', None)) #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, nodes.Const)
@@ -4697,8 +4442,7 @@
self.assertIsNone(third.value)
def test_lookup(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A(object):
def test(self): pass
class B(A):
@@ -4715,8 +4459,7 @@
class X(object):
def test(self):
getattr(self, 'test') #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, BoundMethod)
@@ -4743,48 +4486,41 @@
self.assertEqual(fifth.bound.name, "X")
def test_lambda(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
getattr(lambda x: x, 'f') #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)
class HasattrTest(unittest.TestCase):
def test_inference_errors(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
from missing import Missing
hasattr(Unknown, 'ala') #@
hasattr(Missing, 'bala') #@
hasattr('portocala', Missing) #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)
def test_attribute_is_missing(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A: pass
hasattr(int, 'ala') #@
hasattr({}, 'bala') #@
hasattr(A(), 'portocala') #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertFalse(inferred.value)
def test_attribute_is_not_missing(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class A(object):
def test(self): pass
class B(A):
@@ -4801,19 +4537,16 @@
class X(object):
def test(self):
hasattr(self, 'test') #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertTrue(inferred.value)
def test_lambda(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
hasattr(lambda x: x, 'f') #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertIs(inferred.value, False)
@@ -4844,26 +4577,22 @@
self.assertEqual(inferred.value, expected_value)
def test_yes_when_unknown(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
from unknown import unknown, any, not_any
0 and unknown #@
unknown or 0 #@
any or not_any and unknown #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)
def test_other_nodes(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
def test(): pass
test and 0 #@
1 and test #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertEqual(first.value, 0)
@@ -4901,8 +4630,7 @@
self.assertEqual(inferred.value, expected_value)
def test_callable_methods(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class C:
def test(self): pass
@staticmethod
@@ -4928,34 +4656,29 @@
property #@
D #@
D() #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertTrue(inferred)
def test_inference_errors(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
from unknown import unknown
callable(unknown) #@
def test():
return unknown
callable(test()) #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)
def test_not_callable(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
callable("") #@
callable(1) #@
callable(True) #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertFalse(inferred.value)
@@ -4984,8 +4707,7 @@
self.assertEqual(inferred.value, expected)
def test_bool_bool_special_method(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class FalseClass:
def __bool__(self):
return False
@@ -5012,38 +4734,33 @@
bool(B()) #@
bool(LambdaBoolFalse()) #@
bool(FalseBoolLen()) #@
- """
- )
+ """)
expected = [True, True, False, True, False, False, False]
for node, expected_value in zip(ast_nodes, expected):
inferred = next(node.infer())
self.assertEqual(inferred.value, expected_value)
def test_bool_instance_not_callable(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
class BoolInvalid(object):
__bool__ = 42
class LenInvalid(object):
__len__ = "a"
bool(BoolInvalid()) #@
bool(LenInvalid()) #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)
def test_class_subscript(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class Foo:
def __class_getitem__(cls, *args, **kwargs):
return cls
Foo[int]
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
self.assertEqual(inferred.name, "Foo")
@@ -5118,8 +4835,7 @@
(3,),
(42,),
]
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
def func(*args):
return args
func() #@
@@ -5145,8 +4861,7 @@
func(1, 2) #@
func(1, 2, 3) #@
func(1, 2, *(42, )) #@
- """
- )
+ """)
for node, expected_value in zip(ast_nodes, expected_values):
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Tuple)
@@ -5154,14 +4869,12 @@
def test_multiple_starred_args(self) -> None:
expected_values = [(1, 2, 3), (1, 4, 2, 3, 5, 6, 7)]
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
def func(a, b, *args):
return args
func(1, 2, *(1, ), *(2, 3)) #@
func(1, 2, *(1, ), 4, *(2, 3), 5, *(6, 7)) #@
- """
- )
+ """)
for node, expected_value in zip(ast_nodes, expected_values):
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Tuple)
@@ -5169,16 +4882,14 @@
def test_defaults(self) -> None:
expected_values = [42, 3, 41, 42]
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
def func(a, b, c=42, *args):
return c
func(1, 2) #@
func(1, 2, 3) #@
func(1, 2, c=41) #@
func(1, 2, 42, 41) #@
- """
- )
+ """)
for node, expected_value in zip(ast_nodes, expected_values):
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Const)
@@ -5186,8 +4897,7 @@
def test_kwonly_args(self) -> None:
expected_values = [24, 24, 42, 23, 24, 24, 54]
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
def test(*, f, b): return f
test(f=24, b=33) #@
def test(a, *, f): return f
@@ -5200,8 +4910,7 @@
test(1, 2, 3) #@
test(1, 2, 3, 4) #@
test(1, 2, 3, 4, 5, f=54) #@
- """
- )
+ """)
for node, expected_value in zip(ast_nodes, expected_values):
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Const)
@@ -5209,15 +4918,13 @@
def test_kwargs(self) -> None:
expected = [[("a", 1), ("b", 2), ("c", 3)], [("a", 1)], [("a", "b")]]
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
def test(**kwargs):
return kwargs
test(a=1, b=2, c=3) #@
test(a=1) #@
test(**{'a': 'b'}) #@
- """
- )
+ """)
for node, expected_value in zip(ast_nodes, expected):
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Dict)
@@ -5225,16 +4932,14 @@
self.assertEqual(value, expected_value)
def test_kwargs_and_other_named_parameters(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
def test(a=42, b=24, **kwargs):
return kwargs
test(42, 24, c=3, d=4) #@
test(49, b=24, d=4) #@
test(a=42, b=33, c=3, d=42) #@
test(a=42, **{'c':42}) #@
- """
- )
+ """)
expected_values = [
[("c", 3), ("d", 4)],
[("d", 4)],
@@ -5249,8 +4954,7 @@
def test_kwargs_access_by_name(self) -> None:
expected_values = [42, 42, 42, 24]
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
def test(**kwargs):
return kwargs['f']
test(f=42) #@
@@ -5259,8 +4963,7 @@
def test(f=42, **kwargs):
return kwargs['l']
test(l=24) #@
- """
- )
+ """)
for ast_node, value in zip(ast_nodes, expected_values):
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Const, inferred)
@@ -5268,36 +4971,31 @@
def test_multiple_kwargs(self) -> None:
expected_value = [("a", 1), ("b", 2), ("c", 3), ("d", 4), ("f", 42)]
- ast_node = extract_node(
- """
+ ast_node = extract_node("""
def test(**kwargs):
return kwargs
test(a=1, b=2, **{'c': 3}, **{'d': 4}, f=42) #@
- """
- )
+ """)
inferred = next(ast_node.infer())
self.assertIsInstance(inferred, nodes.Dict)
value = self._get_dict_value(inferred)
self.assertEqual(value, expected_value)
def test_kwargs_are_overridden(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
def test(f):
return f
test(f=23, **{'f': 34}) #@
def test(f=None):
return f
test(f=23, **{'f':23}) #@
- """
- )
+ """)
for ast_node in ast_nodes:
inferred = next(ast_node.infer())
self.assertEqual(inferred, util.Uninferable)
def test_fail_to_infer_args(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
def test(a, **kwargs): return a
test(*missing) #@
test(*object) #@
@@ -5321,16 +5019,14 @@
test(*unknown) #@
def test(*args): return args
test(*unknown) #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)
def test_args_overwritten(self) -> None:
# https://github.com/pylint-dev/astroid/issues/180
- node = extract_node(
- """
+ node = extract_node("""
next = 42
def wrapper(next=next):
next = 24
@@ -5338,8 +5034,7 @@
return next
return test
wrapper()() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
self.assertEqual(len(inferred), 1)
@@ -5365,8 +5060,7 @@
self.assertEqual([elt.value for elt in inferred.elts], expected_value)
def test_slice_inference_error(self) -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
from unknown import unknown
[1, 2, 3][slice(None, unknown, unknown)] #@
[1, 2, 3][slice(None, missing, missing)] #@
@@ -5376,8 +5070,7 @@
[1, 2, 3][slice(1, 2.0, 3.0)] #@
[1, 2, 3][slice()] #@
[1, 2, 3][slice(1, 2, 3, 4)] #@
- """
- )
+ """)
for node in ast_nodes:
self.assertRaises(InferenceError, next, node.infer())
@@ -5495,7 +5188,7 @@
else:
kwargs = {}
- if nums:
+ if nums is not None:
add(*nums)
print(**kwargs)
"""
@@ -5515,13 +5208,11 @@
class ObjectDunderNewTest(unittest.TestCase):
def test_object_dunder_new_is_inferred_if_decorator(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
@object.__new__
class instance(object):
pass
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, Instance)
@@ -5567,10 +5258,32 @@
if result is None:
assert value_node is util.Uninferable
else:
- assert isinstance(value_node, Const)
+ assert isinstance(value_node, nodes.Const)
assert value_node.value == result
+def test_fstring_large_width_no_memory_error() -> None:
+ """MemoryError should not crash inference for f-strings with huge width.
+
+ Regression test for https://github.com/pylint-dev/astroid/issues/2762
+ """
+
+ class OOMInt(int):
+ """An int whose __format__ raises MemoryError, simulating f'{0:>11111111111}'."""
+
+ def __format__(self, spec: str) -> str:
+ raise MemoryError
+
+ node = extract_node("f'{0:>9}'")
+ # Replace the Const value with our OOMInt so format() raises MemoryError
+ # without actually allocating a huge string.
+ fmt_value = node.values[0]
+ fmt_value.value.value = OOMInt(0)
+ inferred = node.inferred()
+ assert len(inferred) == 1
+ assert inferred[0] is util.Uninferable
+
+
def test_augassign_recursion() -> None:
"""Make sure inference doesn't throw a RecursionError.
@@ -5590,8 +5303,7 @@
def test_infer_custom_inherit_from_property() -> None:
- node = extract_node(
- """
+ node = extract_node("""
class custom_property(property):
pass
@@ -5601,34 +5313,30 @@
return 1
MyClass().my_prop
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value == 1
def test_cannot_infer_call_result_for_builtin_methods() -> None:
- node = extract_node(
- """
+ node = extract_node("""
a = "fast"
a
- """
- )
+ """)
inferred = next(node.infer())
lenmeth = next(inferred.igetattr("__len__"))
- with pytest.raises(InferenceError):
- next(lenmeth.infer_call_result(None, None))
+ # Builtin dunder methods now return Uninferable instead of raising InferenceError
+ result = next(lenmeth.infer_call_result(None, None))
+ assert result is util.Uninferable
def test_unpack_dicts_in_assignment() -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
a, b = {1:2, 2:3}
a #@
b #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first_inferred = next(ast_nodes[0].infer())
second_inferred = next(ast_nodes[1].infer())
@@ -5639,40 +5347,33 @@
def test_slice_inference_in_for_loops() -> None:
- node = extract_node(
- """
+ node = extract_node("""
for a, (c, *b) in [(1, (2, 3, 4)), (4, (5, 6))]:
b #@
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.List)
assert inferred.as_string() == "[3, 4]"
- node = extract_node(
- """
+ node = extract_node("""
for a, *b in [(1, 2, 3, 4)]:
b #@
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.List)
assert inferred.as_string() == "[2, 3, 4]"
- node = extract_node(
- """
+ node = extract_node("""
for a, *b in [(1,)]:
b #@
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.List)
assert inferred.as_string() == "[]"
def test_slice_inference_in_for_loops_not_working() -> None:
- ast_nodes = extract_node(
- """
+ ast_nodes = extract_node("""
from unknown import Unknown
for a, *b in something:
b #@
@@ -5680,8 +5381,7 @@
b #@
for a, *b in (1):
b #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
assert inferred == util.Uninferable
@@ -5698,34 +5398,28 @@
def test_unpacking_starred_and_dicts_in_assignment() -> None:
- node = extract_node(
- """
+ node = extract_node("""
a, *b = {1:2, 2:3, 3:4}
b
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.List)
assert inferred.as_string() == "[2, 3]"
- node = extract_node(
- """
+ node = extract_node("""
a, *b = {1:2}
b
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.List)
assert inferred.as_string() == "[]"
def test_unpacking_starred_empty_list_in_assignment() -> None:
- node = extract_node(
- """
+ node = extract_node("""
a, *b, c = [1, 2]
b #@
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.List)
assert inferred.as_string() == "[]"
@@ -5776,8 +5470,7 @@
See https://github.com/pylint-dev/pylint/issues/2199
"""
- node = extract_node(
- """
+ node = extract_node("""
class Base:
def __call__(self):
return self
@@ -5787,8 +5480,7 @@
obj = Sub()
val = obj()
val #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
[val] = node.inferred()
assert isinstance(val, Instance)
@@ -5802,8 +5494,7 @@
@pytest.mark.xfail(reason="Relying on path copy")
def test_call_context_propagation(self):
- n = extract_node(
- """
+ n = extract_node("""
def chest(a):
return a * a
def best(a, b):
@@ -5811,8 +5502,7 @@
def test(a, b, c):
return best(a, b)
test(4, 5, 6) #@
- """
- )
+ """)
assert next(n.infer()).as_string() == "16"
def test_call_starargs_propagation(self) -> None:
@@ -6136,8 +5826,7 @@
def test_attribute_mro_object_inference() -> None:
"""Inference should only infer results from the first available method."""
- inferred = extract_node(
- """
+ inferred = extract_node("""
class A:
def foo(self):
return 1
@@ -6145,30 +5834,24 @@
def foo(self):
return 2
B().foo() #@
- """
- ).inferred()
+ """).inferred()
assert len(inferred) == 1
assert inferred[0].value == 2
def test_inferred_sequence_unpacking_works() -> None:
- inferred = next(
- extract_node(
- """
+ inferred = next(extract_node("""
def test(*args):
return (1, *args)
test(2) #@
- """
- ).infer()
- )
+ """).infer())
assert isinstance(inferred, nodes.Tuple)
assert len(inferred.elts) == 2
assert [value.value for value in inferred.elts] == [1, 2]
def test_recursion_error_inferring_slice() -> None:
- node = extract_node(
- """
+ node = extract_node("""
class MyClass:
def __init__(self):
self._slice = slice(0, 10)
@@ -6178,15 +5861,13 @@
def test(self):
self._slice #@
- """
- )
+ """)
inferred = next(node.infer())
- assert isinstance(inferred, Slice)
+ assert isinstance(inferred, nodes.Slice)
def test_exception_lookup_last_except_handler_wins() -> None:
- node = extract_node(
- """
+ node = extract_node("""
try:
1/0
except ValueError as exc:
@@ -6195,8 +5876,7 @@
1/0
except OSError as exc:
exc #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -6205,16 +5885,14 @@
assert inferred_exc.name == "OSError"
# Two except handlers on the same Try work the same as separate
- node = extract_node(
- """
+ node = extract_node("""
try:
1/0
except ZeroDivisionError as exc:
pass
except ValueError as exc:
exc #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -6224,8 +5902,7 @@
def test_exception_lookup_name_bound_in_except_handler() -> None:
- node = extract_node(
- """
+ node = extract_node("""
try:
1/0
except ValueError:
@@ -6235,8 +5912,7 @@
except OSError:
name = 2
name #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -6246,11 +5922,9 @@
def test_builtin_inference_list_of_exceptions() -> None:
- node = extract_node(
- """
+ node = extract_node("""
tuple([ValueError, TypeError])
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.Tuple)
assert len(inferred.elts) == 2
@@ -6276,23 +5950,19 @@
def test_cannot_getattr_ann_assigns() -> None:
- node = extract_node(
- """
+ node = extract_node("""
class Cls:
ann: int
- """
- )
+ """)
inferred = next(node.infer())
with pytest.raises(AttributeInferenceError):
inferred.getattr("ann")
# But if it had a value, then it would be okay.
- node = extract_node(
- """
+ node = extract_node("""
class Cls:
ann: int = 0
- """
- )
+ """)
inferred = next(node.infer())
values = inferred.getattr("ann")
assert len(values) == 1
@@ -6441,6 +6111,98 @@
assert [third[0].value, third[1].value] == [1, 2]
+def test_ifexp_with_default_arguments() -> None:
+ code = """
+ def with_default(foo: str | None = None):
+ a = 1 if foo else "bar" #@
+
+ def without_default(foo: str):
+ a = 1 if foo else "bar" #@
+
+ def some_ifexps(foo: str | None = None):
+ a = 1 if foo else 2
+ b = 3 if a else 4 #@
+ c = 4 if b else 5 #@
+ d = 5 if not foo else foo #@
+ e = d if not foo else foo #@
+ """
+
+ ast_nodes = extract_node(code)
+
+ first = ast_nodes[0].value.inferred()
+ second = ast_nodes[1].value.inferred()
+ third = ast_nodes[2].value.inferred()
+ fourth = ast_nodes[3].value.inferred()
+ fifth = ast_nodes[4].value.inferred()
+ sixth = ast_nodes[5].value.inferred()
+
+ assert len(first) == 2
+ assert [first[0].value, first[1].value] == [1, "bar"]
+
+ assert len(second) == 2
+ assert [second[0].value, second[1].value] == [1, "bar"]
+
+ assert len(third) == 1
+ assert third[0].value == 3
+
+ assert len(fourth) == 1
+ assert fourth[0].value == 4
+
+ assert len(fifth) == 2
+ assert [fifth[0].value, fifth[1].value] == [5, Uninferable]
+
+ assert len(sixth) == 3
+ assert [sixth[0].value, sixth[1].value, sixth[2].value] == [
+ 5,
+ Uninferable,
+ Uninferable,
+ ]
+
+
+def test_ifexp_with_uninferables() -> None:
+ code = """
+ def truthy_and_falsy():
+ return False if unknown() else True
+
+ def truthy_and_uninferable():
+ return False if unknown() else unknown()
+
+ def calls_truthy_and_falsy():
+ return 1 if truthy_and_falsy() else 2
+
+ def calls_truthy_and_uninferable():
+ return 1 if range(10) else truthy_and_uninferable()
+
+ truthy_and_falsy() #@
+ truthy_and_uninferable() #@
+ calls_truthy_and_falsy() #@
+ calls_truthy_and_uninferable() #@
+ """
+
+ ast_nodes = extract_node(code)
+
+ first = ast_nodes[0].inferred()
+ second = ast_nodes[1].inferred()
+ third = ast_nodes[2].inferred()
+ fourth = ast_nodes[3].inferred()
+
+ assert len(first) == 2
+ assert [first[0].value, first[1].value] == [False, True]
+
+ assert len(second) == 2
+ assert [second[0].value, second[1].value] == [False, Uninferable]
+
+ assert len(third) == 2
+ assert [third[0].value, third[1].value] == [1, 2]
+
+ assert len(fourth) == 3
+ assert [fourth[0].value, fourth[1].value, fourth[2].value] == [
+ 1,
+ False,
+ Uninferable,
+ ]
+
+
def test_assert_last_function_returns_none_on_inference() -> None:
code = """
def check_equal(a, b):
@@ -6648,13 +6410,11 @@
node = extract_node(code)
inferred = next(node.infer())
assert isinstance(inferred, objects.Property)
- property_body = textwrap.dedent(
- """
+ property_body = textwrap.dedent("""
@property
def test(self):
return 42
- """
- )
+ """)
assert inferred.as_string().strip() == property_body.strip()
@@ -6701,16 +6461,14 @@
def test_recursion_error_inferring_builtin_containers() -> None:
- node = extract_node(
- """
+ node = extract_node("""
class Foo:
a = "foo"
inst = Foo()
b = tuple([inst.a]) #@
inst.a = b
- """
- )
+ """)
util.safe_infer(node.targets[0])
@@ -7150,12 +6908,10 @@
operators.
"""
mod1 = parse(
- textwrap.dedent(
- """
+ textwrap.dedent("""
from top1.mod import v as z
w = [1] + z
- """
- ),
+ """),
module_name="top1",
)
parse("v = [2]", module_name="top1.mod")
@@ -7168,12 +6924,10 @@
def test_imported_module_var_inferable2() -> None:
"""Version list of strings."""
mod2 = parse(
- textwrap.dedent(
- """
+ textwrap.dedent("""
from top2.mod import v as z
w = ['1'] + z
- """
- ),
+ """),
module_name="top2",
)
parse("v = ['2']", module_name="top2.mod")
@@ -7186,12 +6940,10 @@
def test_imported_module_var_inferable3() -> None:
"""Version list of strings with a __dunder__ name."""
mod3 = parse(
- textwrap.dedent(
- """
+ textwrap.dedent("""
from top3.mod import __dunder_var__ as v
__dunder_var__ = ['w'] + v
- """
- ),
+ """),
module_name="top",
)
parse("__dunder_var__ = ['v']", module_name="top3.mod")
@@ -7371,14 +7123,10 @@
def test_sys_argv_uninferable() -> None:
"""Regression test for https://github.com/pylint-dev/pylint/issues/7710."""
- a: nodes.List = extract_node(
- textwrap.dedent(
- """
+ a: nodes.List = extract_node(textwrap.dedent("""
import sys
- sys.argv"""
- )
- )
+ sys.argv"""))
sys_argv_value = list(a._infer())
assert len(sys_argv_value) == 1
assert sys_argv_value[0] is Uninferable
@@ -7418,13 +7166,31 @@
def test_joined_str_returns_string(source, expected) -> None:
"""Regression test for https://github.com/pylint-dev/pylint/issues/9947."""
node = extract_node(source)
- assert isinstance(node, Assign)
+ assert isinstance(node, nodes.Assign)
target = node.targets[0]
assert target
inferred = list(target.inferred())
assert len(inferred) == 1
if expected:
- assert isinstance(inferred[0], Const)
+ assert isinstance(inferred[0], nodes.Const)
inferred[0].value.startswith(expected)
else:
assert inferred[0] is Uninferable
+
+
+def test_joined_str_uninferable() -> None:
+ """`thing` is not known, therefore the joinedstring should be
+ Uninferable
+ """
+ code = """
+ def hey(thing):
+ return thing
+
+ f"Hello {hey()}"
+ """
+ joined_str = extract_node(code)
+ constant_value, formatted_value = joined_str.values
+ assert constant_value.value == "Hello "
+ assert formatted_value.value.as_string() == "hey()"
+ inferred = next(joined_str.infer())
+ assert inferred is util.Uninferable
diff --git a/tests/test_inference_calls.py b/tests/test_inference_calls.py
index 31be586..1afe6eb 100644
--- a/tests/test_inference_calls.py
+++ b/tests/test_inference_calls.py
@@ -10,14 +10,12 @@
def test_no_return() -> None:
"""Test function with no return statements."""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def f():
pass
f() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -26,14 +24,12 @@
def test_one_return() -> None:
"""Test function with a single return that always executes."""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def f():
return 1
f() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -46,15 +42,13 @@
Note: currently, inference doesn't handle this type of control flow
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def f(x):
if x:
return 1
f(1) #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -64,8 +58,7 @@
def test_multiple_returns() -> None:
"""Test function with multiple returns."""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def f(x):
if x > 10:
return 1
@@ -75,8 +68,7 @@
return 3
f(100) #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 3
@@ -86,14 +78,12 @@
def test_argument() -> None:
"""Test function whose return value uses its arguments."""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def f(x, y):
return x + y
f(1, 2) #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -103,8 +93,7 @@
def test_inner_call() -> None:
"""Test function where return value is the result of a separate function call."""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def f():
return g()
@@ -112,8 +101,7 @@
return 1
f() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -125,8 +113,7 @@
"""Test function where return value is the result of a separate function call,
with a constant value passed to the inner function.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def f():
return g(1)
@@ -134,8 +121,7 @@
return y + 2
f() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -149,8 +135,7 @@
Currently, this is Uninferable.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def f(x):
return g(x)
@@ -158,8 +143,7 @@
return y + 2
f(1) #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -170,8 +154,7 @@
"""Test method where the return value is based on an instance attribute with a
constant value.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
def __init__(self):
self.x = 1
@@ -180,8 +163,7 @@
return self.x
A().get_x() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -193,8 +175,7 @@
"""Test method where the return value is based on an instance attribute with
multiple possible constant values, across different methods.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
def __init__(self, x):
if x:
@@ -209,8 +190,7 @@
return self.x
A().get_x() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 3
@@ -226,8 +206,7 @@
is guaranteed to override any previous assignments, all possible constant values
are returned.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
def __init__(self, x):
if x:
@@ -243,8 +222,7 @@
return self.x
A().get_x() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 4
@@ -258,8 +236,7 @@
In this case, the return value is Uninferable.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
def __init__(self, x):
self.x = x
@@ -268,8 +245,7 @@
return self.x
A(1).get_x() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -280,8 +256,7 @@
"""Test method where the return value is based on an instance attribute with
a dynamically-set value in the same method.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
# Note: no initializer, so the only assignment happens in get_x
@@ -290,8 +265,7 @@
return self.x
A().get_x(1) #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -305,8 +279,7 @@
This is currently Uninferable.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
def get_x(self, x): # x is unused
return self.x
@@ -315,8 +288,7 @@
self.x = x
A().get_x(10) #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -329,8 +301,7 @@
This is currently Uninferable.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
# Note: no initializer, so the only assignment happens in get_x
@@ -342,8 +313,7 @@
self.x = x
A().get_x() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -360,8 +330,7 @@
This is currently Uninferable.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
# Note: no initializer, so the only assignment happens in get_x
@@ -373,8 +342,7 @@
self.x = x
A().get_x(1) #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -387,8 +355,7 @@
This is currently Uninferable.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
# Note: no initializer, so the only assignment happens in get_x
@@ -400,8 +367,7 @@
self.x = x
A().get_x(1) #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -414,8 +380,7 @@
This is currently Uninferable, until we can infer instance attribute values through
constructor calls.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
def __init__(self, x):
self.x = x
@@ -424,8 +389,7 @@
return self.x + i
A(1)[2] #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
@@ -434,8 +398,7 @@
def test_instance_method() -> None:
"""Tests for instance method, both bound and unbound."""
- nodes_ = builder.extract_node(
- """
+ nodes_ = builder.extract_node("""
class A:
def method(self, x):
return x
@@ -444,8 +407,7 @@
# In this case, the 1 argument is bound to self, which is ignored in the method
A.method(1, 42) #@
- """
- )
+ """)
for node in nodes_:
assert isinstance(node, nodes.NodeNG)
@@ -457,8 +419,7 @@
def test_class_method() -> None:
"""Tests for class method calls, both instance and with the class."""
- nodes_ = builder.extract_node(
- """
+ nodes_ = builder.extract_node("""
class A:
@classmethod
def method(cls, x):
@@ -467,8 +428,7 @@
A.method(42) #@
A().method(42) #@
- """
- )
+ """)
for node in nodes_:
assert isinstance(node, nodes.NodeNG)
@@ -480,8 +440,7 @@
def test_static_method() -> None:
"""Tests for static method calls, both instance and with the class."""
- nodes_ = builder.extract_node(
- """
+ nodes_ = builder.extract_node("""
class A:
@staticmethod
def method(x):
@@ -489,8 +448,7 @@
A.method(42) #@
A().method(42) #@
- """
- )
+ """)
for node in nodes_:
assert isinstance(node, nodes.NodeNG)
@@ -505,8 +463,7 @@
Based on https://github.com/pylint-dev/astroid/issues/1008.
"""
- nodes_ = builder.extract_node(
- """
+ nodes_ = builder.extract_node("""
class A:
def method(self):
return self
@@ -520,8 +477,7 @@
B().method() #@
B.method(B()) #@
A.method(B()) #@
- """
- )
+ """)
expected_names = ["A", "A", "B", "B", "B"]
for node, expected in zip(nodes_, expected_names):
assert isinstance(node, nodes.NodeNG)
@@ -536,8 +492,7 @@
Based on https://github.com/pylint-dev/astroid/issues/1008.
"""
- nodes_ = builder.extract_node(
- """
+ nodes_ = builder.extract_node("""
class A:
@classmethod
def method(cls):
@@ -551,8 +506,7 @@
B().method() #@
B.method() #@
- """
- )
+ """)
expected_names = ["A", "A", "B", "B"]
for node, expected in zip(nodes_, expected_names):
assert isinstance(node, nodes.NodeNG)
@@ -567,8 +521,7 @@
Based on https://github.com/pylint-dev/pylint/issues/4220.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A:
def f(self):
return 42
@@ -584,8 +537,7 @@
B().a.f() #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
inferred = node.inferred()
assert len(inferred) == 1
diff --git a/tests/test_lookup.py b/tests/test_lookup.py
index bcee8f6..edcef7a 100644
--- a/tests/test_lookup.py
+++ b/tests/test_lookup.py
@@ -3,6 +3,7 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Tests for the astroid variable lookup capabilities."""
+
import functools
import unittest
@@ -155,12 +156,10 @@
def test_list_comp_target(self) -> None:
"""Test the list comprehension target."""
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
ten = [ var for var in range(10) ]
var
- """
- )
+ """)
var = astroid.body[1].value
self.assertRaises(NameInferenceError, var.inferred)
@@ -199,12 +198,10 @@
self.assertEqual(xnames[1].lookup("i")[1][0].lineno, 3)
def test_set_comp_closure(self) -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
ten = { var for var in range(10) }
var
- """
- )
+ """)
var = astroid.body[1].value
self.assertRaises(NameInferenceError, var.inferred)
@@ -249,34 +246,29 @@
self.assertEqual(xnames[0].lookup("i")[1][0].lineno, 3)
def test_lambda_nested(self) -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
f = lambda x: (
lambda y: x + y)
- """
- )
+ """)
xnames = [n for n in astroid.nodes_of_class(nodes.Name) if n.name == "x"]
self.assertEqual(len(xnames[0].lookup("x")[1]), 1)
self.assertEqual(xnames[0].lookup("x")[1][0].lineno, 2)
def test_function_nested(self) -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
def f1(x):
def f2(y):
return x + y
return f2
- """
- )
+ """)
xnames = [n for n in astroid.nodes_of_class(nodes.Name) if n.name == "x"]
self.assertEqual(len(xnames[0].lookup("x")[1]), 1)
self.assertEqual(xnames[0].lookup("x")[1][0].lineno, 2)
def test_class_variables(self) -> None:
# Class variables are NOT available within nested scopes.
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
class A:
a = 10
@@ -289,8 +281,7 @@
class _Inner:
inner_a = a + 1
- """
- )
+ """)
names = [n for n in astroid.nodes_of_class(nodes.Name) if n.name == "a"]
self.assertEqual(len(names), 4)
for name in names:
@@ -298,8 +289,7 @@
def test_class_in_function(self) -> None:
# Function variables are available within classes, including methods
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
def f():
x = 10
class A:
@@ -314,8 +304,7 @@
class _Inner:
inner_a = x + 1
- """
- )
+ """)
names = [n for n in astroid.nodes_of_class(nodes.Name) if n.name == "x"]
self.assertEqual(len(names), 5)
for name in names:
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 9a7bbdb..e420e6b 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -15,7 +15,7 @@
import pytest
import astroid
-from astroid import manager, test_utils
+from astroid import manager, nodes, test_utils
from astroid.const import IS_JYTHON, IS_PYPY, PY312_PLUS
from astroid.exceptions import (
AstroidBuildingError,
@@ -107,7 +107,7 @@
try:
for name in ("foo", "bar", "baz"):
module = self.manager.ast_from_module_name("package." + name)
- self.assertIsInstance(module, astroid.Module)
+ self.assertIsInstance(module, nodes.Module)
finally:
sys.path = origpath
@@ -120,8 +120,15 @@
def test_identify_old_namespace_package_protocol(self) -> None:
# Like the above cases, this package follows the old namespace package protocol
# astroid currently assumes such packages are in sys.modules, so import it
- # pylint: disable-next=import-outside-toplevel
- import tests.testdata.python3.data.path_pkg_resources_1.package.foo as _ # noqa
+ with warnings.catch_warnings():
+ warnings.filterwarnings(
+ "ignore",
+ category=UserWarning,
+ message=".*pkg_resources is deprecated.*",
+ )
+
+ # pylint: disable-next=import-outside-toplevel
+ import tests.testdata.python3.data.path_pkg_resources_1.package.foo as _ # noqa
self.assertTrue(
util.is_namespace("tests.testdata.python3.data.path_pkg_resources_1")
@@ -174,10 +181,10 @@
try:
module = self.manager.ast_from_module_name("namespace_pep_420.module")
- self.assertIsInstance(module, astroid.Module)
+ self.assertIsInstance(module, nodes.Module)
self.assertEqual(module.name, "namespace_pep_420.module")
var = next(module.igetattr("var"))
- self.assertIsInstance(var, astroid.Const)
+ self.assertIsInstance(var, nodes.Const)
self.assertEqual(var.value, 42)
finally:
for _ in range(2):
@@ -195,7 +202,7 @@
module = self.manager.ast_from_module_name("foogle.fax")
submodule = next(module.igetattr("a"))
value = next(submodule.igetattr("x"))
- self.assertIsInstance(value, astroid.Const)
+ self.assertIsInstance(value, nodes.Const)
with self.assertRaises(AstroidImportError):
self.manager.ast_from_module_name("foogle.moogle")
finally:
@@ -277,6 +284,17 @@
finally:
os.remove(linked_file_name)
+ def test_ast_from_module_name_pyz_with_submodule(self) -> None:
+ with self._restore_package_cache():
+ archive_path = os.path.join(resources.RESOURCE_PATH, "x.zip")
+ sys.path.insert(0, archive_path)
+ module = self.manager.ast_from_module_name("xxx.test")
+ self.assertEqual(module.name, "xxx.test")
+ end = os.path.join(archive_path, "xxx", "test")
+ self.assertTrue(
+ module.file.endswith(end), f"{module.file} doesn't endswith {end}"
+ )
+
def test_zip_import_data(self) -> None:
"""Check if zip_import_data works."""
with self._restore_package_cache():
diff --git a/tests/test_modutils.py b/tests/test_modutils.py
index e1b4be3..e85e7ff 100644
--- a/tests/test_modutils.py
+++ b/tests/test_modutils.py
@@ -3,6 +3,7 @@
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Unit tests for module modutils (module manipulation utilities)."""
+
import email
import logging
import os
@@ -20,7 +21,6 @@
import astroid
from astroid import modutils
-from astroid.const import PY310_PLUS
from astroid.interpreter._import import spec
from . import resources
@@ -152,6 +152,31 @@
self.assertEqual(modutils.get_module_part(".", modutils.__file__), ".")
+class IsSubpathTest(unittest.TestCase):
+ """Tests for modutils._is_subpath,
+ which is used internally by modutils.modpath_from_file."""
+
+ @unittest.skipUnless(sys.platform == "win32", "Windows-specific path test")
+ def test_is_subpath_with_trailing_separator_unc_path(self) -> None:
+ self.assertTrue(
+ modutils._is_subpath(
+ "\\\\Mac\\Code\\tests\\test_resources.py",
+ # UNC path with trailing separator
+ "\\\\mac\\code\\",
+ )
+ )
+
+ @unittest.skipUnless(sys.platform == "win32", "Windows-specific path test")
+ def test_is_subpath_without_trailing_separator_unc_path(self) -> None:
+ self.assertTrue(
+ modutils._is_subpath(
+ "\\\\Mac\\Code\\tests\\test_resources.py",
+ # UNC path without trailing separator
+ "\\\\mac\\code",
+ )
+ )
+
+
class ModPathFromFileTest(unittest.TestCase):
"""Given an absolute file path return the python module's path as a list."""
@@ -162,7 +187,7 @@
)
def test_raise_modpath_from_file_exception(self) -> None:
- self.assertRaises(Exception, modutils.modpath_from_file, "/turlututu")
+ self.assertRaises(ImportError, modutils.modpath_from_file, "/turlututu")
def test_import_symlink_with_source_outside_of_path(self) -> None:
with tempfile.NamedTemporaryFile() as tmpfile:
@@ -335,6 +360,22 @@
os.path.normpath(module) + "i",
)
+ def test_nonstandard_extension(self) -> None:
+ package = resources.find("pyi_data/find_test")
+ modules = [
+ os.path.join(package, "__init__.weird_ext"),
+ os.path.join(package, "standalone_file.weird_ext"),
+ ]
+ for module in modules:
+ self.assertEqual(
+ modutils.get_source_file(module, prefer_stubs=True),
+ module,
+ )
+ self.assertEqual(
+ modutils.get_source_file(module),
+ module,
+ )
+
class IsStandardModuleTest(resources.SysPathSetup, unittest.TestCase):
"""
@@ -486,18 +527,6 @@
assert not modutils.module_in_path("astroid", datadir)
-class BackportStdlibNamesTest(resources.SysPathSetup, unittest.TestCase):
- """
- Verify backport raises exception on newer versions
- """
-
- @pytest.mark.skipif(not PY310_PLUS, reason="Backport valid on <=3.9")
- def test_import_error(self) -> None:
- with pytest.raises(AssertionError):
- # pylint: disable-next=import-outside-toplevel, unused-import
- from astroid import _backport_stdlib_names # noqa
-
-
class IsRelativeTest(unittest.TestCase):
def test_known_values_is_relative_1(self) -> None:
self.assertTrue(modutils.is_relative("utils", email.__path__[0]))
diff --git a/tests/test_nodes.py b/tests/test_nodes.py
index ffa5115..4bba86b 100644
--- a/tests/test_nodes.py
+++ b/tests/test_nodes.py
@@ -13,6 +13,7 @@
import sys
import textwrap
import unittest
+import warnings
from typing import Any
import pytest
@@ -28,29 +29,24 @@
transforms,
util,
)
-from astroid.const import IS_PYPY, PY310_PLUS, PY312_PLUS, Context
+from astroid.const import (
+ IS_PYPY,
+ PY311_PLUS,
+ PY312_PLUS,
+ PY313_PLUS,
+ PY314_PLUS,
+ Context,
+)
from astroid.context import InferenceContext
from astroid.exceptions import (
AstroidBuildingError,
AstroidSyntaxError,
+ AstroidTypeError,
AttributeInferenceError,
StatementMissing,
)
-from astroid.nodes.node_classes import (
- AssignAttr,
- AssignName,
- Attribute,
- Call,
- ImportFrom,
- Tuple,
-)
-from astroid.nodes.scoped_nodes import (
- SYNTHETIC_ROOT,
- ClassDef,
- FunctionDef,
- GeneratorExp,
- Module,
-)
+from astroid.nodes.node_classes import UNATTACHED_UNKNOWN
+from astroid.nodes.scoped_nodes import SYNTHETIC_ROOT
from tests.testdata.python3.recursion_error import LONG_CHAINED_METHOD_CALL
from . import resources
@@ -60,7 +56,7 @@
class AsStringTest(resources.SysPathSetup, unittest.TestCase):
def test_tuple_as_string(self) -> None:
- def build(string: str) -> Tuple:
+ def build(string: str) -> nodes.Tuple:
return abuilder.string_build(string).body[0].value
self.assertEqual(build("1,").as_string(), "(1, )")
@@ -69,38 +65,32 @@
self.assertEqual(build("1, 2, 3").as_string(), "(1, 2, 3)")
def test_func_signature_issue_185(self) -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
def test(a, b, c=42, *, x=42, **kwargs):
print(a, b, c, args)
- """
- )
+ """)
node = parse(code)
self.assertEqual(node.as_string().strip(), code.strip())
def test_as_string_for_list_containing_uninferable(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def foo():
bar = [arg] * 1
- """
- )
+ """)
binop = node.body[0].value
inferred = next(binop.infer())
self.assertEqual(inferred.as_string(), "[Uninferable]")
self.assertEqual(binop.as_string(), "[arg] * 1")
def test_frozenset_as_string(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
frozenset((1, 2, 3)) #@
frozenset({1, 2, 3}) #@
frozenset([1, 2, 3,]) #@
frozenset(None) #@
frozenset(1) #@
- """
- )
+ """)
ast_nodes = [next(node.infer()) for node in ast_nodes]
assert isinstance(ast_nodes, list)
self.assertEqual(ast_nodes[0].as_string(), "frozenset((1, 2, 3))")
@@ -114,11 +104,31 @@
ast = abuilder.string_build("raise_string(*args, **kwargs)").body[0]
self.assertEqual(ast.as_string(), "raise_string(*args, **kwargs)")
- def test_module_as_string(self) -> None:
- """Check as_string on a whole module prepared to be returned identically."""
+ @pytest.mark.skipif(PY314_PLUS, reason="return in finally is now a syntax error")
+ def test_module_as_string_pre_3_14(self) -> None:
+ """Check as_string on a whole module prepared to be returned identically for py < 3.14."""
+ self.maxDiff = None
module = resources.build_file("data/module.py", "data.module")
with open(resources.find("data/module.py"), encoding="utf-8") as fobj:
- self.assertMultiLineEqual(module.as_string(), fobj.read())
+ # Ignore comments in python file
+ data_str = "\n".join(
+ [s for s in fobj.read().split("\n") if not s.lstrip().startswith("# ")]
+ )
+ self.assertMultiLineEqual(module.as_string(), data_str)
+
+ @pytest.mark.skipif(
+ not PY314_PLUS, reason="return in finally is now a syntax error"
+ )
+ def test_module_as_string(self) -> None:
+ """Check as_string on a whole module prepared to be returned identically for py > 3.14."""
+ self.maxDiff = None
+ module = resources.build_file("data/module3.14.py", "data.module3.14")
+ with open(resources.find("data/module3.14.py"), encoding="utf-8") as fobj:
+ # Ignore comments in python file
+ data_str = "\n".join(
+ [s for s in fobj.read().split("\n") if not s.lstrip().startswith("# ")]
+ )
+ self.assertMultiLineEqual(module.as_string(), data_str)
def test_module2_as_string(self) -> None:
"""Check as_string on a whole module prepared to be returned identically."""
@@ -281,8 +291,10 @@
@staticmethod
def test_as_string_unknown() -> None:
- assert nodes.Unknown().as_string() == "Unknown.Unknown()"
- assert nodes.Unknown(lineno=1, col_offset=0).as_string() == "Unknown.Unknown()"
+ unknown1 = nodes.Unknown(parent=SYNTHETIC_ROOT)
+ unknown2 = nodes.Unknown(lineno=1, col_offset=0, parent=SYNTHETIC_ROOT)
+ assert unknown1.as_string() == "Unknown.Unknown()"
+ assert unknown2.as_string() == "Unknown.Unknown()"
@staticmethod
@pytest.mark.skipif(
@@ -303,15 +315,34 @@
class AsStringTypeParamNodes(unittest.TestCase):
@staticmethod
def test_as_string_type_alias() -> None:
- ast = abuilder.string_build("type Point = tuple[float, float]")
- type_alias = ast.body[0]
- assert type_alias.as_string().strip() == "Point"
+ ast1 = abuilder.string_build("type Point = tuple[float, float]")
+ type_alias1 = ast1.body[0]
+ assert type_alias1.as_string().strip() == "type Point = tuple[float, float]"
+ ast2 = abuilder.string_build(
+ "type Point[T, **P] = tuple[float, T, Callable[P, None]]"
+ )
+ type_alias2 = ast2.body[0]
+ assert (
+ type_alias2.as_string().strip()
+ == "type Point[T, **P] = tuple[float, T, Callable[P, None]]"
+ )
@staticmethod
def test_as_string_type_var() -> None:
- ast = abuilder.string_build("type Point[T] = tuple[float, float]")
+ ast = abuilder.string_build("type Point[T: int | str] = tuple[float, float]")
type_var = ast.body[0].type_params[0]
- assert type_var.as_string().strip() == "T"
+ assert type_var.as_string().strip() == "T: int | str"
+
+ @staticmethod
+ @pytest.mark.skipif(
+ not PY313_PLUS, reason="Type parameter defaults were added in 313"
+ )
+ def test_as_string_type_var_default() -> None:
+ ast = abuilder.string_build(
+ "type Point[T: int | str = int] = tuple[float, float]"
+ )
+ type_var = ast.body[0].type_params[0]
+ assert type_var.as_string().strip() == "T: int | str = int"
@staticmethod
def test_as_string_type_var_tuple() -> None:
@@ -320,10 +351,40 @@
assert type_var_tuple.as_string().strip() == "*Ts"
@staticmethod
+ @pytest.mark.skipif(
+ not PY313_PLUS, reason="Type parameter defaults were added in 313"
+ )
+ def test_as_string_type_var_tuple_defaults() -> None:
+ ast = abuilder.string_build("type Alias[*Ts = tuple[int, str]] = tuple[*Ts]")
+ type_var_tuple = ast.body[0].type_params[0]
+ assert type_var_tuple.as_string().strip() == "*Ts = tuple[int, str]"
+
+ @staticmethod
def test_as_string_param_spec() -> None:
ast = abuilder.string_build("type Alias[**P] = Callable[P, int]")
param_spec = ast.body[0].type_params[0]
- assert param_spec.as_string().strip() == "P"
+ assert param_spec.as_string().strip() == "**P"
+
+ @staticmethod
+ @pytest.mark.skipif(
+ not PY313_PLUS, reason="Type parameter defaults were added in 313"
+ )
+ def test_as_string_param_spec_defaults() -> None:
+ ast = abuilder.string_build("type Alias[**P = [str, int]] = Callable[P, int]")
+ param_spec = ast.body[0].type_params[0]
+ assert param_spec.as_string().strip() == "**P = [str, int]"
+
+ @staticmethod
+ def test_as_string_class_type_params() -> None:
+ code = abuilder.string_build("class A[T, **P]: ...")
+ cls_node = code.body[0]
+ assert cls_node.as_string().strip() == "class A[T, **P]:\n ..."
+
+ @staticmethod
+ def test_as_string_function_type_params() -> None:
+ code = abuilder.string_build("def func[T, **P](): ...")
+ func_node = code.body[0]
+ assert func_node.as_string().strip() == "def func[T, **P]():\n ..."
class _NodeTest(unittest.TestCase):
@@ -332,7 +393,7 @@
CODE = ""
@property
- def astroid(self) -> Module:
+ def astroid(self) -> nodes.Module:
try:
return self.__class__.__dict__["CODE_Astroid"]
except KeyError:
@@ -527,9 +588,7 @@
ast = self.module["modutils"]
self.assertEqual(ast.as_string(), "from astroid import modutils")
ast = self.module["NameNode"]
- self.assertEqual(
- ast.as_string(), "from astroid.nodes.node_classes import Name as NameNode"
- )
+ self.assertEqual(ast.as_string(), "from astroid.nodes import Name as NameNode")
ast = self.module["os"]
self.assertEqual(ast.as_string(), "import os.path")
code = """from . import here
@@ -646,11 +705,9 @@
self._test("a")
def test_str_kind(self):
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
const = u"foo"
- """
- )
+ """)
assert isinstance(node.value, nodes.Const)
assert node.value.value == "foo"
assert node.value.kind, "u"
@@ -682,8 +739,7 @@
"""Test if the frame of NamedExpr is correctly set for certain types
of parent nodes.
"""
- module = builder.parse(
- """
+ module = builder.parse("""
def func(var_1):
pass
@@ -706,8 +762,7 @@
pass
COMPREHENSION = [y for i in (1, 2) if (y := i ** 2)]
- """
- )
+ """)
function = module.body[0]
assert function.args.frame() == function
assert function.args.frame() == function
@@ -745,8 +800,7 @@
"""Test if the scope of NamedExpr is correctly set for certain types
of parent nodes.
"""
- module = builder.parse(
- """
+ module = builder.parse("""
def func(var_1):
pass
@@ -769,8 +823,7 @@
pass
COMPREHENSION = [y for i in (1, 2) if (y := i ** 2)]
- """
- )
+ """)
function = module.body[0]
assert function.args.scope() == function
@@ -796,11 +849,9 @@
class AnnAssignNodeTest(unittest.TestCase):
def test_primitive(self) -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
test: int = 5
- """
- )
+ """)
assign = builder.extract_node(code)
self.assertIsInstance(assign, nodes.AnnAssign)
self.assertEqual(assign.target.name, "test")
@@ -809,11 +860,9 @@
self.assertEqual(assign.simple, 1)
def test_primitive_without_initial_value(self) -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
test: str
- """
- )
+ """)
assign = builder.extract_node(code)
self.assertIsInstance(assign, nodes.AnnAssign)
self.assertEqual(assign.target.name, "test")
@@ -821,39 +870,33 @@
self.assertEqual(assign.value, None)
def test_complex(self) -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
test: Dict[List[str]] = {}
- """
- )
+ """)
assign = builder.extract_node(code)
self.assertIsInstance(assign, nodes.AnnAssign)
self.assertEqual(assign.target.name, "test")
- self.assertIsInstance(assign.annotation, astroid.Subscript)
- self.assertIsInstance(assign.value, astroid.Dict)
+ self.assertIsInstance(assign.annotation, nodes.Subscript)
+ self.assertIsInstance(assign.value, nodes.Dict)
def test_as_string(self) -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
print()
test: int = 5
test2: str
test3: List[Dict[str, str]] = []
- """
- )
+ """)
ast = abuilder.string_build(code)
self.assertEqual(ast.as_string().strip(), code.strip())
class ArgumentsNodeTC(unittest.TestCase):
def test_linenumbering(self) -> None:
- ast = builder.parse(
- """
+ ast = builder.parse("""
def func(a,
b): pass
x = lambda x: None
- """
- )
+ """)
self.assertEqual(ast["func"].args.fromlineno, 2)
self.assertFalse(ast["func"].args.is_statement)
xlambda = next(ast["x"].infer())
@@ -862,23 +905,19 @@
self.assertFalse(xlambda.args.is_statement)
def test_kwoargs(self) -> None:
- ast = builder.parse(
- """
+ ast = builder.parse("""
def func(*, x):
pass
- """
- )
+ """)
args = ast["func"].args
assert isinstance(args, nodes.Arguments)
assert args.is_argument("x")
assert args.kw_defaults == [None]
- ast = builder.parse(
- """
+ ast = builder.parse("""
def func(*, x = "default"):
pass
- """
- )
+ """)
args = ast["func"].args
assert isinstance(args, nodes.Arguments)
assert args.is_argument("x")
@@ -887,12 +926,10 @@
assert args.kw_defaults[0].value == "default"
def test_positional_only(self):
- ast = builder.parse(
- """
+ ast = builder.parse("""
def func(x, /, y):
pass
- """
- )
+ """)
args = ast["func"].args
self.assertTrue(args.is_argument("x"))
self.assertTrue(args.is_argument("y"))
@@ -907,14 +944,12 @@
# https://bitbucket.org/logilab/astroid/issue/91, which tests
# that UnboundMethod doesn't call super when doing .getattr.
- ast = builder.parse(
- """
+ ast = builder.parse("""
class A(object):
def test(self):
pass
meth = A.test
- """
- )
+ """)
node = next(ast["meth"].infer())
with self.assertRaises(AttributeInferenceError):
node.getattr("__missssing__")
@@ -924,97 +959,290 @@
class BoundMethodNodeTest(unittest.TestCase):
- def test_is_property(self) -> None:
- ast = builder.parse(
- """
- import abc
+ def _is_property(self, ast: nodes.Module, prop: str) -> None:
+ inferred = next(ast[prop].infer())
+ self.assertIsInstance(inferred, nodes.Const, prop)
+ self.assertEqual(inferred.value, 42, prop)
- def cached_property():
- # Not a real decorator, but we don't care
- pass
- def reify():
- # Same as cached_property
- pass
- def lazy_property():
- pass
- def lazyproperty():
- pass
- def lazy(): pass
+ def test_is_standard_property(self) -> None:
+ # Test to make sure the Python-provided property decorators
+ # are properly interpreted as properties
+ ast = builder.parse("""
+ import abc
+ import functools
+
class A(object):
@property
- def builtin_property(self):
- return 42
+ def builtin_property(self): return 42
+
@abc.abstractproperty
- def abc_property(self):
- return 42
+ def abc_property(self): return 42
+
+ @property
+ @abc.abstractmethod
+ def abstractmethod_property(self): return 42
+
+ @functools.cached_property
+ def functools_property(self): return 42
+
+ cls = A()
+ builtin_p = cls.builtin_property
+ abc_p = cls.abc_property
+ abstractmethod_p = cls.abstractmethod_property
+ functools_p = cls.functools_property
+ """)
+ for prop in (
+ "builtin_p",
+ "abc_p",
+ "abstractmethod_p",
+ "functools_p",
+ ):
+ self._is_property(ast, prop)
+
+ @pytest.mark.skipif(not PY311_PLUS, reason="Uses enum.property introduced in 3.11")
+ def test_is_standard_property_py311(self) -> None:
+ # Test to make sure the Python-provided property decorators
+ # are properly interpreted as properties
+ ast = builder.parse("""
+ import enum
+
+ class A(object):
+ @enum.property
+ def enum_property(self): return 42
+
+ cls = A()
+ enum_p = cls.enum_property
+ """)
+ self._is_property(ast, "enum_p")
+
+ def test_is_possible_property(self) -> None:
+ # Test to make sure that decorators with POSSIBLE_PROPERTIES names
+ # are properly interpreted as properties
+ ast = builder.parse("""
+ # Not real decorators, but we don't care
+ def cachedproperty(): pass
+ def cached_property(): pass
+ def reify(): pass
+ def lazy_property(): pass
+ def lazyproperty(): pass
+ def lazy(): pass
+ def lazyattribute(): pass
+ def lazy_attribute(): pass
+ def LazyProperty(): pass
+ def DynamicClassAttribute(): pass
+
+ class A(object):
+ @cachedproperty
+ def cachedproperty(self): return 42
+
@cached_property
def cached_property(self): return 42
+
@reify
def reified(self): return 42
+
@lazy_property
def lazy_prop(self): return 42
+
@lazyproperty
def lazyprop(self): return 42
- def not_prop(self): pass
+
@lazy
def decorated_with_lazy(self): return 42
+ @lazyattribute
+ def lazyattribute(self): return 42
+
+ @lazy_attribute
+ def lazy_attribute(self): return 42
+
+ @LazyProperty
+ def LazyProperty(self): return 42
+
+ @DynamicClassAttribute
+ def DynamicClassAttribute(self): return 42
+
cls = A()
- builtin_property = cls.builtin_property
- abc_property = cls.abc_property
+ cachedp = cls.cachedproperty
cached_p = cls.cached_property
reified = cls.reified
- not_prop = cls.not_prop
lazy_prop = cls.lazy_prop
lazyprop = cls.lazyprop
decorated_with_lazy = cls.decorated_with_lazy
- """
- )
+ lazya = cls.lazyattribute
+ lazy_a = cls.lazy_attribute
+ LazyP = cls.LazyProperty
+ DynamicClassA = cls.DynamicClassAttribute
+ """)
for prop in (
- "builtin_property",
- "abc_property",
+ "cachedp",
"cached_p",
"reified",
"lazy_prop",
"lazyprop",
"decorated_with_lazy",
+ "lazya",
+ "lazy_a",
+ "LazyP",
+ "DynamicClassA",
+ ):
+ self._is_property(ast, prop)
+
+ def test_is_standard_property_subclass(self) -> None:
+ # Test to make sure that subclasses of the Python-provided property decorators
+ # are properly interpreted as properties
+ ast = builder.parse("""
+ import abc
+ import functools
+ from typing import Generic, TypeVar
+
+ class user_property(property): pass
+ class user_abc_property(abc.abstractproperty): pass
+ class user_functools_property(functools.cached_property): pass
+ T = TypeVar('T')
+ class annotated_user_functools_property(functools.cached_property[T], Generic[T]): pass
+
+ class A(object):
+ @user_property
+ def user_property(self): return 42
+
+ @user_abc_property
+ def user_abc_property(self): return 42
+
+ @user_functools_property
+ def user_functools_property(self): return 42
+
+ @annotated_user_functools_property
+ def annotated_user_functools_property(self): return 42
+
+ cls = A()
+ user_p = cls.user_property
+ user_abc_p = cls.user_abc_property
+ user_functools_p = cls.user_functools_property
+ annotated_user_functools_p = cls.annotated_user_functools_property
+ """)
+ for prop in (
+ "user_p",
+ "user_abc_p",
+ "user_functools_p",
+ "annotated_user_functools_p",
+ ):
+ self._is_property(ast, prop)
+
+ @pytest.mark.skipif(not PY311_PLUS, reason="Uses enum.property introduced in 3.11")
+ def test_is_standard_property_subclass_py311(self) -> None:
+ # Test to make sure that subclasses of the Python-provided property decorators
+ # are properly interpreted as properties
+ ast = builder.parse("""
+ import enum
+
+ class user_enum_property(enum.property): pass
+
+ class A(object):
+ @user_enum_property
+ def user_enum_property(self): return 42
+
+ cls = A()
+ user_enum_p = cls.user_enum_property
+ """)
+ self._is_property(ast, "user_enum_p")
+
+ @pytest.mark.skipif(not PY312_PLUS, reason="Uses 3.12 generic typing syntax")
+ def test_is_standard_property_subclass_py312(self) -> None:
+ ast = builder.parse("""
+ from functools import cached_property
+
+ class annotated_user_cached_property[T](cached_property[T]):
+ pass
+
+ class A(object):
+ @annotated_user_cached_property
+ def annotated_user_cached_property(self): return 42
+
+ cls = A()
+ annotated_user_cached_p = cls.annotated_user_cached_property
+ """)
+ self._is_property(ast, "annotated_user_cached_p")
+
+ def test_is_not_property(self) -> None:
+ ast = builder.parse("""
+ from collections.abc import Iterator
+
+ class cached_property: pass
+ # If a decorator is named cached_property, we will accept it as a property,
+ # even if it isn't functools.cached_property.
+ # However, do not extend the same leniency to superclasses of decorators.
+ class wrong_superclass_type1(cached_property): pass
+ class wrong_superclass_type2(cached_property[float]): pass
+ cachedproperty = { float: int }
+ class wrong_superclass_type3(cachedproperty[float]): pass
+ class wrong_superclass_type4(Iterator[float]): pass
+
+ class A(object):
+ def no_decorator(self): return 42
+
+ def property(self): return 42
+
+ @wrong_superclass_type1
+ def wrong_superclass_type1(self): return 42
+
+ @wrong_superclass_type2
+ def wrong_superclass_type2(self): return 42
+
+ @wrong_superclass_type3
+ def wrong_superclass_type3(self): return 42
+
+ @wrong_superclass_type4
+ def wrong_superclass_type4(self): return 42
+
+ cls = A()
+ no_decorator = cls.no_decorator
+ not_prop = cls.property
+ bad_superclass1 = cls.wrong_superclass_type1
+ bad_superclass2 = cls.wrong_superclass_type2
+ bad_superclass3 = cls.wrong_superclass_type3
+ bad_superclass4 = cls.wrong_superclass_type4
+ """)
+ for prop in (
+ "no_decorator",
+ "not_prop",
+ "bad_superclass1",
+ "bad_superclass2",
+ "bad_superclass3",
+ "bad_superclass4",
):
inferred = next(ast[prop].infer())
- self.assertIsInstance(inferred, nodes.Const, prop)
- self.assertEqual(inferred.value, 42, prop)
-
- inferred = next(ast["not_prop"].infer())
- self.assertIsInstance(inferred, bases.BoundMethod)
+ self.assertIsInstance(inferred, bases.BoundMethod)
class AliasesTest(unittest.TestCase):
def setUp(self) -> None:
self.transformer = transforms.TransformVisitor()
- def parse_transform(self, code: str) -> Module:
+ def parse_transform(self, code: str) -> nodes.Module:
module = parse(code, apply_transforms=False)
return self.transformer.visit(module)
def test_aliases(self) -> None:
- def test_from(node: ImportFrom) -> ImportFrom:
+ def test_from(node: nodes.ImportFrom) -> nodes.ImportFrom:
node.names = [*node.names, ("absolute_import", None)]
return node
- def test_class(node: ClassDef) -> ClassDef:
+ def test_class(node: nodes.ClassDef) -> nodes.ClassDef:
node.name = "Bar"
return node
- def test_function(node: FunctionDef) -> FunctionDef:
+ def test_function(node: nodes.FunctionDef) -> nodes.FunctionDef:
node.name = "another_test"
return node
- def test_callfunc(node: Call) -> Call | None:
+ def test_callfunc(node: nodes.Call) -> nodes.Call | None:
if node.func.name == "Foo":
node.func.name = "Bar"
return node
return None
- def test_assname(node: AssignName) -> AssignName | None:
+ def test_assname(node: nodes.AssignName) -> nodes.AssignName | None:
if node.name == "foo":
return nodes.AssignName(
"bar",
@@ -1026,19 +1254,19 @@
)
return None
- def test_assattr(node: AssignAttr) -> AssignAttr:
+ def test_assattr(node: nodes.AssignAttr) -> nodes.AssignAttr:
if node.attrname == "a":
node.attrname = "b"
return node
return None
- def test_getattr(node: Attribute) -> Attribute:
+ def test_getattr(node: nodes.Attribute) -> nodes.Attribute:
if node.attrname == "a":
node.attrname = "b"
return node
return None
- def test_genexpr(node: GeneratorExp) -> GeneratorExp:
+ def test_genexpr(node: nodes.GeneratorExp) -> nodes.GeneratorExp:
if node.elt.value == 1:
node.elt = nodes.Const(2, node.lineno, node.col_offset, node.parent)
return node
@@ -1141,46 +1369,38 @@
self.assertEqual(ast_node.as_string().strip(), code.strip())
def test_await_as_string(self) -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
async def function():
await 42
await x[0]
(await x)[0]
await (x + y)[0]
- """
- )
+ """)
self._test_await_async_as_string(code)
def test_asyncwith_as_string(self) -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
async def function():
async with 42:
pass
- """
- )
+ """)
self._test_await_async_as_string(code)
def test_asyncfor_as_string(self) -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
async def function():
async for i in range(10):
await 42
- """
- )
+ """)
self._test_await_async_as_string(code)
def test_decorated_async_def_as_string(self) -> None:
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
@decorator
async def function():
async for i in range(10):
await 42
- """
- )
+ """)
self._test_await_async_as_string(code)
@@ -1231,74 +1451,65 @@
def test_unknown() -> None:
"""Test Unknown node."""
- assert isinstance(next(nodes.Unknown().infer()), type(util.Uninferable))
- assert isinstance(nodes.Unknown().name, str)
- assert isinstance(nodes.Unknown().qname(), str)
+ assert isinstance(next(UNATTACHED_UNKNOWN.infer()), type(util.Uninferable))
+ assert isinstance(UNATTACHED_UNKNOWN.name, str)
+ assert isinstance(UNATTACHED_UNKNOWN.qname(), str)
def test_type_comments_with() -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
with a as b: # type: int
pass
with a as b: # type: ignore[name-defined]
pass
- """
- )
+ """)
node = module.body[0]
ignored_node = module.body[1]
- assert isinstance(node.type_annotation, astroid.Name)
+ assert isinstance(node.type_annotation, nodes.Name)
assert ignored_node.type_annotation is None
def test_type_comments_for() -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
for a, b in [1, 2, 3]: # type: List[int]
pass
for a, b in [1, 2, 3]: # type: ignore[name-defined]
pass
- """
- )
+ """)
node = module.body[0]
ignored_node = module.body[1]
- assert isinstance(node.type_annotation, astroid.Subscript)
+ assert isinstance(node.type_annotation, nodes.Subscript)
assert node.type_annotation.as_string() == "List[int]"
assert ignored_node.type_annotation is None
def test_type_coments_assign() -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
a, b = [1, 2, 3] # type: List[int]
a, b = [1, 2, 3] # type: ignore[name-defined]
- """
- )
+ """)
node = module.body[0]
ignored_node = module.body[1]
- assert isinstance(node.type_annotation, astroid.Subscript)
+ assert isinstance(node.type_annotation, nodes.Subscript)
assert node.type_annotation.as_string() == "List[int]"
assert ignored_node.type_annotation is None
def test_type_comments_invalid_expression() -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
a, b = [1, 2, 3] # type: something completely invalid
a, b = [1, 2, 3] # typeee: 2*+4
a, b = [1, 2, 3] # type: List[int
- """
- )
+ """)
for node in module.body:
assert node.type_annotation is None
def test_type_comments_invalid_function_comments() -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
def func(
# type: () -> int # inside parentheses
):
@@ -1312,16 +1523,14 @@
def func2():
# type: List[int
pass
- """
- )
+ """)
for node in module.body:
assert node.type_comment_returns is None
assert node.type_comment_args is None
def test_type_comments_function() -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
def func():
# type: (int) -> str
pass
@@ -1331,12 +1540,11 @@
def func2():
# type: (int, int, str, List[int]) -> List[int]
pass
- """
- )
+ """)
expected_annotations = [
- (["int"], astroid.Name, "str"),
- (["int", "int", "int"], astroid.Tuple, "(str, str)"),
- (["int", "int", "str", "List[int]"], astroid.Subscript, "List[int]"),
+ (["int"], nodes.Name, "str"),
+ (["int", "int", "int"], nodes.Tuple, "(str, str)"),
+ (["int", "int", "str", "List[int]"], nodes.Subscript, "List[int]"),
]
for node, (expected_args, expected_returns_type, expected_returns_string) in zip(
module.body, expected_annotations
@@ -1350,8 +1558,7 @@
def test_type_comments_arguments() -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
def func(
a, # type: int
):
@@ -1372,8 +1579,7 @@
):
# type: (...) -> List[int]
pass
- """
- )
+ """)
expected_annotations = [
["int"],
["int", "int", "int"],
@@ -1381,7 +1587,7 @@
]
for node, expected_args in zip(module.body, expected_annotations):
assert len(node.type_comment_args) == 1
- assert isinstance(node.type_comment_args[0], astroid.Const)
+ assert isinstance(node.type_comment_args[0], nodes.Const)
assert node.type_comment_args[0].value == Ellipsis
assert len(node.args.type_comment_args) == len(expected_args)
for expected_arg, actual_arg in zip(expected_args, node.args.type_comment_args):
@@ -1389,8 +1595,7 @@
def test_type_comments_posonly_arguments() -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
def f_arg_comment(
a, # type: int
b, # type: int
@@ -1403,14 +1608,13 @@
):
# type: (...) -> None
pass
- """
- )
+ """)
expected_annotations = [
[["int", "int"], ["Optional[int]", "Optional[int]"], ["float", "float"]]
]
for node, expected_types in zip(module.body, expected_annotations):
assert len(node.type_comment_args) == 1
- assert isinstance(node.type_comment_args[0], astroid.Const)
+ assert isinstance(node.type_comment_args[0], nodes.Const)
assert node.type_comment_args[0].value == Ellipsis
type_comments = [
node.args.type_comment_posonlyargs,
@@ -1436,8 +1640,7 @@
def test_is_generator_for_yield_assignments() -> None:
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
class A:
def test(self):
a = yield
@@ -1446,8 +1649,7 @@
yield a
a = A()
a.test
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, astroid.BoundMethod)
assert bool(inferred.is_generator())
@@ -1455,15 +1657,13 @@
class AsyncGeneratorTest(unittest.TestCase):
def test_async_generator(self):
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
async def a_iter(n):
for i in range(1, n + 1):
yield i
await asyncio.sleep(1)
a_iter(2) #@
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, bases.AsyncGenerator)
assert inferred.getattr("__aiter__")
@@ -1474,14 +1674,12 @@
def test_f_string_correct_line_numbering() -> None:
"""Test that we generate correct line numbers for f-strings."""
- node = astroid.extract_node(
- """
+ node = astroid.extract_node("""
def func_foo(arg_bar, arg_foo):
dict_foo = {}
f'{arg_bar.attr_bar}' #@
- """
- )
+ """)
assert node.lineno == 5
assert node.last_child().lineno == 5
assert node.last_child().last_child().lineno == 5
@@ -1584,13 +1782,11 @@
def test_get_doc() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
def func():
"Docstring"
return 1
- """
- )
+ """)
node: nodes.FunctionDef = astroid.extract_node(code) # type: ignore[assignment]
assert isinstance(node.doc_node, nodes.Const)
assert node.doc_node.value == "Docstring"
@@ -1599,13 +1795,11 @@
assert node.doc_node.end_lineno == 2
assert node.doc_node.end_col_offset == 15
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
def func():
...
return 1
- """
- )
+ """)
node = astroid.extract_node(code)
assert node.doc_node is None
@@ -1631,15 +1825,15 @@
assert len(type_comments) == 1
type_comment = type_comments[0]
- assert isinstance(type_comment, astroid.Attribute)
- assert isinstance(type_comment.parent, astroid.Expr)
- assert isinstance(type_comment.parent.parent, astroid.Arguments)
+ assert isinstance(type_comment, nodes.Attribute)
+ assert isinstance(type_comment.parent, nodes.Expr)
+ assert isinstance(type_comment.parent.parent, nodes.Arguments)
def test_const_itered() -> None:
code = 'a = "string"'
node = astroid.extract_node(code).value
- assert isinstance(node, astroid.Const)
+ assert isinstance(node, nodes.Const)
itered = node.itered()
assert len(itered) == 6
assert [elem.value for elem in itered] == list("string")
@@ -1681,12 +1875,10 @@
assert bool(node.is_generator())
-@pytest.mark.skipif(not PY310_PLUS, reason="pattern matching was added in PY310")
class TestPatternMatching:
@staticmethod
def test_match_simple():
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
match status:
case 200:
pass
@@ -1696,8 +1888,7 @@
pass
case _:
pass
- """
- ).strip()
+ """).strip()
node = builder.extract_node(code)
assert node.as_string() == code
assert isinstance(node, nodes.Match)
@@ -1709,12 +1900,12 @@
assert isinstance(case0.pattern, nodes.MatchValue)
assert (
- isinstance(case0.pattern.value, astroid.Const)
+ isinstance(case0.pattern.value, nodes.Const)
and case0.pattern.value.value == 200
)
assert list(case0.pattern.get_children()) == [case0.pattern.value]
assert case0.guard is None
- assert isinstance(case0.body[0], astroid.Pass)
+ assert isinstance(case0.body[0], nodes.Pass)
assert list(case0.get_children()) == [case0.pattern, case0.body[0]]
assert isinstance(case1.pattern, nodes.MatchOr)
@@ -1740,13 +1931,11 @@
@staticmethod
def test_match_sequence():
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
match status:
case [x, 2, _, *rest] as y if x > 2:
pass
- """
- ).strip()
+ """).strip()
node = builder.extract_node(code)
assert node.as_string() == code
assert isinstance(node, nodes.Match)
@@ -1794,15 +1983,13 @@
@staticmethod
def test_match_mapping():
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
match status:
case {0: x, 1: _}:
pass
case {**rest}:
pass
- """
- ).strip()
+ """).strip()
node = builder.extract_node(code)
assert node.as_string() == code
assert isinstance(node, nodes.Match)
@@ -1844,15 +2031,13 @@
@staticmethod
def test_match_class():
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
match x:
case Point2D(0, a):
pass
case Point3D(x=0, y=1, z=b):
pass
- """
- ).strip()
+ """).strip()
node = builder.extract_node(code)
assert node.as_string() == code
assert isinstance(node, nodes.Match)
@@ -1920,8 +2105,7 @@
@staticmethod
def test_return_from_match():
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
def return_from_match(x):
match x:
case 10:
@@ -1930,14 +2114,46 @@
return -1
return_from_match(10) #@
- """
- ).strip()
+ """).strip()
node = builder.extract_node(code)
inferred = node.inferred()
assert len(inferred) == 2
assert [inf.value for inf in inferred] == [10, -1]
+@pytest.mark.skipif(not PY314_PLUS, reason="TemplateStr was added in PY314")
+class TestTemplateString:
+ @staticmethod
+ def test_template_string_simple() -> None:
+ code = textwrap.dedent("""
+ name = "Foo"
+ place = 3
+ t"{name} finished {place!r:ordinal}" #@
+ """).strip()
+ node = builder.extract_node(code)
+ assert node.as_string() == "t'{name} finished {place!r:ordinal}'"
+ assert isinstance(node, nodes.TemplateStr)
+ assert len(node.values) == 3
+ value = node.values[0]
+ assert isinstance(value, nodes.Interpolation)
+ assert isinstance(value.value, nodes.Name)
+ assert value.value.name == "name"
+ assert value.str == "name"
+ assert value.conversion == -1
+ assert value.format_spec is None
+ value = node.values[1]
+ assert isinstance(value, nodes.Const)
+ assert value.pytype() == "builtins.str"
+ assert value.value == " finished "
+ value = node.values[2]
+ assert isinstance(value, nodes.Interpolation)
+ assert isinstance(value.value, nodes.Name)
+ assert value.value.name == "place"
+ assert value.str == "place"
+ assert value.conversion == ord("r")
+ assert isinstance(value.format_spec, nodes.JoinedStr)
+
+
@pytest.mark.parametrize(
"node",
[
@@ -1963,7 +2179,7 @@
"NodeNG" in param_type.annotation
or "SuccessfulInferenceResult" in param_type.annotation
):
- args[name] = nodes.Unknown()
+ args[name] = UNATTACHED_UNKNOWN
elif "str" in param_type.annotation:
args[name] = ""
else:
@@ -2007,3 +2223,73 @@
node = extract_node("def fruit(seeds, flavor='good', *, peel='maybe'): ...")
assert node.args.default_value("flavor").value == "good"
+
+
+def test_arguments_annotations():
+ node = extract_node(
+ "def fruit(eat: str, /, peel: bool, *args: int, trim: float, **kwargs: bytes): ..."
+ )
+ assert isinstance(node.args, nodes.Arguments)
+ annotation_names = [
+ ann.name for ann in node.args.get_annotations() if isinstance(ann, nodes.Name)
+ ]
+ assert all(
+ name in annotation_names for name in ("str", "bool", "int", "float", "bytes")
+ )
+
+
+def test_deprecated_nodes_import_from_toplevel():
+ # pylint: disable=import-outside-toplevel,no-name-in-module
+ with pytest.raises(
+ DeprecationWarning, match="importing 'For' from 'astroid' is deprecated"
+ ):
+ from astroid import For
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore", DeprecationWarning)
+ from astroid import For
+
+ assert For is nodes.For
+
+ # This should not raise a DeprecationWarning
+ # pylint: disable-next=unused-import
+ from astroid import builtin_lookup
+
+
+def test_str_long_name_no_crash() -> None:
+ """str() should not crash with ValueError on nodes with long names.
+
+ Regression test for https://github.com/pylint-dev/astroid/issues/2764
+ """
+ long_name = "a" * 200
+ code = f"class {long_name}:\n pass"
+ module = parse(code)
+ # This should not raise ValueError('width must be != 0')
+ result = str(module.body[0])
+ assert long_name in result
+
+
+def test_str_large_int_no_crash() -> None:
+ """str() should not crash with ValueError on nodes with large integer values.
+
+ Regression test for https://github.com/pylint-dev/astroid/issues/2785
+ """
+ code = "x = 10 ** 5000"
+ module = parse(code)
+ # Infer the BinOp to get a Const with a huge int value
+ inferred = next(module.body[0].value.infer())
+ assert isinstance(inferred.value, int)
+ # This should not raise ValueError about integer string conversion limit
+ result = str(inferred)
+ assert "int" in result
+
+
+def test_str_large_int_getitem_no_crash() -> None:
+ """getitem error message should not crash with large integer values."""
+ code = "x = 10 ** 5000"
+ module = parse(code)
+ inferred = next(module.body[0].value.infer())
+ assert isinstance(inferred.value, int)
+ # Trigger the error path that formats the value
+ with pytest.raises(AstroidTypeError, match=r"too large to display|int"):
+ inferred.getitem(nodes.Const(0))
diff --git a/tests/test_nodes_lineno.py b/tests/test_nodes_lineno.py
index 875d21d..bbc1e8c 100644
--- a/tests/test_nodes_lineno.py
+++ b/tests/test_nodes_lineno.py
@@ -4,11 +4,9 @@
import textwrap
-import pytest
-
import astroid
from astroid import builder, nodes
-from astroid.const import PY310_PLUS, PY312_PLUS
+from astroid.const import PY312_PLUS
class TestLinenoColOffset:
@@ -19,16 +17,14 @@
@staticmethod
def test_end_lineno_container() -> None:
"""Container nodes: List, Tuple, Set."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
[1, 2, 3] #@
[ #@
1, 2, 3
]
(1, 2, 3) #@
{1, 2, 3} #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 4
@@ -55,8 +51,7 @@
@staticmethod
def test_end_lineno_name() -> None:
"""Name, Assign, AssignName, Delete, DelName."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
var = 42 #@
var #@
del var #@
@@ -64,8 +59,7 @@
var2 = ( #@
1, 2, 3
)
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 4
@@ -103,16 +97,14 @@
@staticmethod
def test_end_lineno_attribute() -> None:
"""Attribute, AssignAttr, DelAttr."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
class X:
var = 42
X.var2 = 2 #@
X.var2 #@
del X.var2 #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 3
@@ -144,11 +136,9 @@
@staticmethod
def test_end_lineno_call() -> None:
"""Call, Keyword."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
func(arg1, arg2=value) #@
- """
- ).strip()
+ """).strip()
c1 = builder.extract_node(code)
assert isinstance(c1, nodes.Call)
assert isinstance(c1.func, nodes.Name)
@@ -174,13 +164,11 @@
@staticmethod
def test_end_lineno_assignment() -> None:
"""Assign, AnnAssign, AugAssign."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
var = 2 #@
var2: int = 2 #@
var3 += 2 #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 3
@@ -223,8 +211,7 @@
@staticmethod
def test_end_lineno_mix_stmts() -> None:
"""Assert, Break, Continue, Global, Nonlocal, Pass, Raise, Return, Expr."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
assert True, "Some message" #@
break #@
continue #@
@@ -234,8 +221,7 @@
raise Exception from ex #@
return 42 #@
var #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 9
@@ -305,14 +291,12 @@
@staticmethod
def test_end_lineno_mix_nodes() -> None:
"""Await, Starred, Yield, YieldFrom."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
await func #@
*args #@
yield 42 #@
yield from (1, 2) #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 4
@@ -351,14 +335,12 @@
@staticmethod
def test_end_lineno_ops() -> None:
"""BinOp, BoolOp, UnaryOp, Compare."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
x + y #@
a and b #@
-var #@
a < b #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 4
@@ -406,8 +388,7 @@
@staticmethod
def test_end_lineno_if() -> None:
"""If, IfExp, NamedExpr."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
if ( #@
var := 2 #@
):
@@ -416,8 +397,7 @@
pass
2 if True else 1 #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 3
@@ -463,8 +443,7 @@
@staticmethod
def test_end_lineno_for() -> None:
"""For, AsyncFor."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
for i in lst: #@
pass
else:
@@ -472,8 +451,7 @@
async for i in lst: #@
pass
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 2
@@ -511,16 +489,14 @@
@staticmethod
def test_end_lineno_const() -> None:
"""Const (int, str, bool, None, bytes, ellipsis)."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
2 #@
"Hello" #@
True #@
None #@
b"01" #@
... #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 6
@@ -558,8 +534,7 @@
def test_end_lineno_function() -> None:
"""FunctionDef, AsyncFunctionDef, Decorators, Lambda, Arguments."""
# pylint: disable = too-many-statements
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
def func( #@
a: int = 0, /,
var: int = 1, *args: Any,
@@ -573,8 +548,7 @@
pass
lambda x: 2 #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 3
@@ -591,6 +565,8 @@
assert (f1.body[0].lineno, f1.body[0].col_offset) == (6, 4)
assert (f1.body[0].end_lineno, f1.body[0].end_col_offset) == (6, 8)
+ assert (f1.args.lineno, f1.args.end_lineno) == (2, 4)
+
# pos only arguments
# TODO fix column offset: arg -> arg (AssignName)
assert isinstance(f1.args.posonlyargs[0], nodes.AssignName)
@@ -669,14 +645,12 @@
@staticmethod
def test_end_lineno_dict() -> None:
"""Dict, DictUnpack."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
{ #@
1: "Hello",
**{2: "World"} #@
}
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 2
@@ -699,8 +673,7 @@
@staticmethod
def test_end_lineno_try() -> None:
"""Try, ExceptHandler."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
try: #@
pass
except KeyError as ex:
@@ -718,8 +691,7 @@
pass
finally:
pass
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 2
@@ -762,13 +734,11 @@
@staticmethod
def test_end_lineno_subscript() -> None:
"""Subscript, Slice, (ExtSlice, Index)."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
var[0] #@
var[1:2:1] #@
var[1:2, 2] #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 3
@@ -809,14 +779,12 @@
@staticmethod
def test_end_lineno_import() -> None:
"""Import, ImportFrom."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
import a.b #@
import a as x #@
from . import x #@
from .a import y as y #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 4
@@ -843,16 +811,14 @@
@staticmethod
def test_end_lineno_with() -> None:
"""With, AsyncWith."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
with open(file) as fp, \\
open(file2) as fp2: #@
pass
async with open(file) as fp: #@
pass
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 2
@@ -893,14 +859,12 @@
@staticmethod
def test_end_lineno_while() -> None:
"""While."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
while 2:
pass
else:
pass
- """
- ).strip()
+ """).strip()
w1 = builder.extract_node(code)
assert isinstance(w1, nodes.While)
assert isinstance(w1.test, nodes.Const)
@@ -918,12 +882,10 @@
@staticmethod
def test_end_lineno_string() -> None:
"""FormattedValue, JoinedStr."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
f"Hello World: {42.1234:02d}" #@
f"Hello: {name=}" #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 2
@@ -987,14 +949,12 @@
assert (s4.value.end_lineno, s4.value.end_col_offset) == (2, 14)
@staticmethod
- @pytest.mark.skipif(not PY310_PLUS, reason="pattern matching was added in PY310")
def test_end_lineno_match() -> None:
"""Match, MatchValue, MatchSingleton, MatchSequence, MatchMapping,
MatchClass, MatchStar, MatchOr, MatchAs.
"""
# pylint: disable = too-many-statements
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
match x: #@
case 200 if True: #@
pass
@@ -1010,8 +970,7 @@
pass
case 200 as c: #@
pass
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 8
@@ -1116,14 +1075,12 @@
@staticmethod
def test_end_lineno_comprehension() -> None:
"""ListComp, SetComp, DictComp, GeneratorExpr."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
[x for x in var] #@
{x for x in var} #@
{x: y for x, y in var} #@
(x for x in var) #@
- """
- ).strip()
+ """).strip()
ast_nodes = builder.extract_node(code)
assert isinstance(ast_nodes, list) and len(ast_nodes) == 4
@@ -1169,14 +1126,12 @@
@staticmethod
def test_end_lineno_class() -> None:
"""ClassDef, Keyword."""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
@decorator1
@decorator2
class X(Parent, var=42):
pass
- """
- ).strip()
+ """).strip()
c1 = builder.extract_node(code)
assert isinstance(c1, nodes.ClassDef)
assert isinstance(c1.decorators, nodes.Decorators)
diff --git a/tests/test_nodes_position.py b/tests/test_nodes_position.py
index 452cd05..1e93839 100644
--- a/tests/test_nodes_position.py
+++ b/tests/test_nodes_position.py
@@ -19,8 +19,7 @@
>>> class A(Parent):
>>> ^^^^^^^
"""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
class A: #@
...
@@ -40,8 +39,7 @@
@decorator
class F: #@
...
- """
- ).strip()
+ """).strip()
ast_nodes: list[nodes.NodeNG] = builder.extract_node(code) # type: ignore[assignment]
a = ast_nodes[0]
@@ -75,8 +73,7 @@
>>> def func(var: int = 42):
>>> ^^^^^^^^
"""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
def a(): #@
...
@@ -92,8 +89,7 @@
@decorator
def e(): #@
...
- """
- ).strip()
+ """).strip()
ast_nodes: list[nodes.NodeNG] = builder.extract_node(code) # type: ignore[assignment]
a = ast_nodes[0]
@@ -123,8 +119,7 @@
>>> async def func(var: int = 42):
>>> ^^^^^^^^^^^^^^
"""
- code = textwrap.dedent(
- """
+ code = textwrap.dedent("""
async def a(): #@
...
@@ -140,8 +135,7 @@
@decorator
async def e(): #@
...
- """
- ).strip()
+ """).strip()
ast_nodes: list[nodes.NodeNG] = builder.extract_node(code) # type: ignore[assignment]
a = ast_nodes[0]
diff --git a/tests/test_object_model.py b/tests/test_object_model.py
index 9ad4d39..89d27ae 100644
--- a/tests/test_object_model.py
+++ b/tests/test_object_model.py
@@ -38,54 +38,50 @@
)
assert isinstance(ast_nodes, list)
cls = next(ast_nodes[0].infer())
- self.assertIsInstance(cls, astroid.ClassDef)
+ self.assertIsInstance(cls, nodes.ClassDef)
self.assertEqual(cls.name, "A")
module = next(ast_nodes[1].infer())
- self.assertIsInstance(module, astroid.Const)
+ self.assertIsInstance(module, nodes.Const)
self.assertEqual(module.value, "fake_module")
doc = next(ast_nodes[2].infer())
- self.assertIsInstance(doc, astroid.Const)
+ self.assertIsInstance(doc, nodes.Const)
self.assertEqual(doc.value, "test")
dunder_dict = next(ast_nodes[3].infer())
- self.assertIsInstance(dunder_dict, astroid.Dict)
- attr = next(dunder_dict.getitem(astroid.Const("a")).infer())
- self.assertIsInstance(attr, astroid.Const)
+ self.assertIsInstance(dunder_dict, nodes.Dict)
+ attr = next(dunder_dict.getitem(nodes.Const("a")).infer())
+ self.assertIsInstance(attr, nodes.Const)
self.assertEqual(attr.value, 42)
@pytest.mark.xfail(reason="Instance lookup cannot override object model")
def test_instance_local_attributes_overrides_object_model(self):
# The instance lookup needs to be changed in order for this to work.
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
class A:
@property
def __dict__(self):
return []
A().__dict__
- """
- )
+ """)
inferred = next(ast_node.infer())
- self.assertIsInstance(inferred, astroid.List)
+ self.assertIsInstance(inferred, nodes.List)
self.assertEqual(inferred.elts, [])
class BoundMethodModelTest(unittest.TestCase):
def test_bound_method_model(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class A:
def test(self): pass
a = A()
a.test.__func__ #@
a.test.__self__ #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
func = next(ast_nodes[0].infer())
- self.assertIsInstance(func, astroid.FunctionDef)
+ self.assertIsInstance(func, nodes.FunctionDef)
self.assertEqual(func.name, "test")
self_ = next(ast_nodes[1].infer())
@@ -95,8 +91,7 @@
class UnboundMethodModelTest(unittest.TestCase):
def test_unbound_method_model(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class A:
def test(self): pass
t = A.test
@@ -106,55 +101,55 @@
t.im_class #@
t.im_func #@
t.im_self #@
- """
- )
+ t.__doc__ #@
+ """)
assert isinstance(ast_nodes, list)
cls = next(ast_nodes[0].infer())
- self.assertIsInstance(cls, astroid.ClassDef)
+ self.assertIsInstance(cls, nodes.ClassDef)
unbound_name = "function"
self.assertEqual(cls.name, unbound_name)
func = next(ast_nodes[1].infer())
- self.assertIsInstance(func, astroid.FunctionDef)
+ self.assertIsInstance(func, nodes.FunctionDef)
self.assertEqual(func.name, "test")
self_ = next(ast_nodes[2].infer())
- self.assertIsInstance(self_, astroid.Const)
+ self.assertIsInstance(self_, nodes.Const)
self.assertIsNone(self_.value)
self.assertEqual(cls.name, next(ast_nodes[3].infer()).name)
self.assertEqual(func, next(ast_nodes[4].infer()))
self.assertIsNone(next(ast_nodes[5].infer()).value)
+ doc = next(ast_nodes[6].infer())
+ self.assertIsInstance(doc, nodes.Const)
+ self.assertIsNone(doc.value)
+
class ClassModelTest(unittest.TestCase):
def test_priority_to_local_defined_values(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
class A:
__doc__ = "first"
A.__doc__ #@
- """
- )
+ """)
inferred = next(ast_node.infer())
- self.assertIsInstance(inferred, astroid.Const)
+ self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, "first")
def test_class_model_correct_mro_subclasses_proxied(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class A(object):
pass
A.mro #@
A.__subclasses__ #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertIsInstance(inferred, astroid.BoundMethod)
- self.assertIsInstance(inferred._proxied, astroid.FunctionDef)
- self.assertIsInstance(inferred.bound, astroid.ClassDef)
+ self.assertIsInstance(inferred._proxied, nodes.FunctionDef)
+ self.assertIsInstance(inferred.bound, nodes.ClassDef)
self.assertEqual(inferred.bound.name, "type")
def test_class_model(self) -> None:
@@ -181,68 +176,63 @@
)
assert isinstance(ast_nodes, list)
module = next(ast_nodes[0].infer())
- self.assertIsInstance(module, astroid.Const)
+ self.assertIsInstance(module, nodes.Const)
self.assertEqual(module.value, "fake_module")
name = next(ast_nodes[1].infer())
- self.assertIsInstance(name, astroid.Const)
+ self.assertIsInstance(name, nodes.Const)
self.assertEqual(name.value, "A")
qualname = next(ast_nodes[2].infer())
- self.assertIsInstance(qualname, astroid.Const)
+ self.assertIsInstance(qualname, nodes.Const)
self.assertEqual(qualname.value, "fake_module.A")
doc = next(ast_nodes[3].infer())
- self.assertIsInstance(doc, astroid.Const)
+ self.assertIsInstance(doc, nodes.Const)
self.assertEqual(doc.value, "test")
mro = next(ast_nodes[4].infer())
- self.assertIsInstance(mro, astroid.Tuple)
+ self.assertIsInstance(mro, nodes.Tuple)
self.assertEqual([cls.name for cls in mro.elts], ["A", "object"])
called_mro = next(ast_nodes[5].infer())
self.assertEqual(called_mro.elts, mro.elts)
base_nodes = next(ast_nodes[6].infer())
- self.assertIsInstance(base_nodes, astroid.Tuple)
+ self.assertIsInstance(base_nodes, nodes.Tuple)
self.assertEqual([cls.name for cls in base_nodes.elts], ["object"])
cls = next(ast_nodes[7].infer())
- self.assertIsInstance(cls, astroid.ClassDef)
+ self.assertIsInstance(cls, nodes.ClassDef)
self.assertEqual(cls.name, "type")
cls_dict = next(ast_nodes[8].infer())
- self.assertIsInstance(cls_dict, astroid.Dict)
+ self.assertIsInstance(cls_dict, nodes.Dict)
subclasses = next(ast_nodes[9].infer())
- self.assertIsInstance(subclasses, astroid.List)
+ self.assertIsInstance(subclasses, nodes.List)
self.assertEqual([cls.name for cls in subclasses.elts], ["B", "C"])
class ModuleModelTest(unittest.TestCase):
def test_priority_to_local_defined_values(self) -> None:
- ast_node = astroid.parse(
- """
+ ast_node = astroid.parse("""
__file__ = "mine"
- """
- )
+ """)
file_value = next(ast_node.igetattr("__file__"))
- self.assertIsInstance(file_value, astroid.Const)
+ self.assertIsInstance(file_value, nodes.Const)
self.assertEqual(file_value.value, "mine")
def test__path__not_a_package(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
import sys
sys.__path__ #@
- """
- )
+ """)
with self.assertRaises(InferenceError):
next(ast_node.infer())
def test_module_model(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
import xml
xml.__path__ #@
xml.__name__ #@
@@ -265,33 +255,35 @@
xml.__setattr__ #@
xml.__reduce_ex__ #@
xml.__lt__ #@
+ xml.__le__ #@
xml.__eq__ #@
+ xml.__ne__ #@
+ xml.__ge__ #@
xml.__gt__ #@
xml.__format__ #@
- xml.__delattr___ #@
+ xml.__delattr__ #@
xml.__getattribute__ #@
xml.__hash__ #@
xml.__dir__ #@
xml.__call__ #@
xml.__closure__ #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
path = next(ast_nodes[0].infer())
- self.assertIsInstance(path, astroid.List)
- self.assertIsInstance(path.elts[0], astroid.Const)
+ self.assertIsInstance(path, nodes.List)
+ self.assertIsInstance(path.elts[0], nodes.Const)
self.assertEqual(path.elts[0].value, xml.__path__[0])
name = next(ast_nodes[1].infer())
- self.assertIsInstance(name, astroid.Const)
+ self.assertIsInstance(name, nodes.Const)
self.assertEqual(name.value, "xml")
doc = next(ast_nodes[2].infer())
- self.assertIsInstance(doc, astroid.Const)
+ self.assertIsInstance(doc, nodes.Const)
self.assertEqual(doc.value, xml.__doc__)
file_ = next(ast_nodes[3].infer())
- self.assertIsInstance(file_, astroid.Const)
+ self.assertIsInstance(file_, nodes.Const)
self.assertEqual(file_.value, xml.__file__.replace(".pyc", ".py"))
for ast_node in ast_nodes[4:7]:
@@ -299,11 +291,11 @@
self.assertIs(inferred, astroid.Uninferable)
package = next(ast_nodes[7].infer())
- self.assertIsInstance(package, astroid.Const)
+ self.assertIsInstance(package, nodes.Const)
self.assertEqual(package.value, "xml")
dict_ = next(ast_nodes[8].infer())
- self.assertIsInstance(dict_, astroid.Dict)
+ self.assertIsInstance(dict_, nodes.Dict)
init_ = next(ast_nodes[9].infer())
assert isinstance(init_, bases.BoundMethod)
@@ -324,38 +316,38 @@
new_ = next(ast_nodes[10].infer())
assert isinstance(new_, bases.BoundMethod)
- # The following nodes are just here for theoretical completeness,
- # and they either return Uninferable or raise InferenceError.
- for ast_node in ast_nodes[11:28]:
+ # Inherited attributes return Uninferable.
+ for ast_node in ast_nodes[11:29]:
+ inferred = next(ast_node.infer())
+ self.assertIs(inferred, astroid.Uninferable)
+
+ # Attributes that don't exist on modules raise InferenceError.
+ for ast_node in ast_nodes[29:31]:
with pytest.raises(InferenceError):
next(ast_node.infer())
class FunctionModelTest(unittest.TestCase):
def test_partial_descriptor_support(self) -> None:
- bound, result = builder.extract_node(
- """
+ bound, result = builder.extract_node("""
class A(object): pass
def test(self): return 42
f = test.__get__(A(), A)
f #@
f() #@
- """
- )
+ """)
bound = next(bound.infer())
self.assertIsInstance(bound, astroid.BoundMethod)
self.assertEqual(bound._proxied._proxied.name, "test")
result = next(result.infer())
- self.assertIsInstance(result, astroid.Const)
+ self.assertIsInstance(result, nodes.Const)
self.assertEqual(result.value, 42)
def test___get__has_extra_params_defined(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def test(self): return 42
test.__get__
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, astroid.BoundMethod)
args = inferred.args.args
@@ -363,12 +355,10 @@
self.assertEqual([arg.name for arg in args], ["self", "type"])
def test__get__and_positional_only_args(self):
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def test(self, a, b, /, c): return a + b + c
test.__get__(test)(1, 2, 3)
- """
- )
+ """)
inferred = next(node.infer())
assert inferred is util.Uninferable
@@ -376,36 +366,31 @@
def test_descriptor_not_inferrring_self(self):
# We can't infer __get__(X, Y)() when the bounded function
# uses self, because of the tree's parent not being propagating good enough.
- result = builder.extract_node(
- """
+ result = builder.extract_node("""
class A(object):
x = 42
def test(self): return self.x
f = test.__get__(A(), A)
f() #@
- """
- )
+ """)
result = next(result.infer())
- self.assertIsInstance(result, astroid.Const)
+ self.assertIsInstance(result, nodes.Const)
self.assertEqual(result.value, 42)
def test_descriptors_binding_invalid(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class A: pass
def test(self): return 42
test.__get__()() #@
test.__get__(2, 3, 4) #@
- """
- )
+ """)
for node in ast_nodes:
with self.assertRaises(InferenceError):
next(node.infer())
def test_descriptor_error_regression(self) -> None:
"""Make sure the following code does node cause an exception."""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class MyClass:
text = "MyText"
@@ -418,8 +403,7 @@
cl = MyClass().mymethod2()()
cl #@
- """
- )
+ """)
assert isinstance(node, nodes.NodeNG)
[const] = node.inferred()
assert const.value == "MyText"
@@ -449,45 +433,52 @@
func.__reduce_ex__ #@
func.__lt__ #@
+ func.__le__ #@
func.__eq__ #@
+ func.__ne__ #@
+ func.__ge__ #@
func.__gt__ #@
func.__format__ #@
- func.__delattr___ #@
+ func.__delattr__ #@
func.__getattribute__ #@
func.__hash__ #@
func.__dir__ #@
func.__class__ #@
func.__setattr__ #@
+ func.__builtins__ #@
+ func.__getstate__ #@
+ func.__init_subclass__ #@
+ func.__type_params__ #@
''',
module_name="fake_module",
)
assert isinstance(ast_nodes, list)
name = next(ast_nodes[0].infer())
- self.assertIsInstance(name, astroid.Const)
+ self.assertIsInstance(name, nodes.Const)
self.assertEqual(name.value, "func")
doc = next(ast_nodes[1].infer())
- self.assertIsInstance(doc, astroid.Const)
+ self.assertIsInstance(doc, nodes.Const)
self.assertEqual(doc.value, "test")
qualname = next(ast_nodes[2].infer())
- self.assertIsInstance(qualname, astroid.Const)
+ self.assertIsInstance(qualname, nodes.Const)
self.assertEqual(qualname.value, "fake_module.func")
module = next(ast_nodes[3].infer())
- self.assertIsInstance(module, astroid.Const)
+ self.assertIsInstance(module, nodes.Const)
self.assertEqual(module.value, "fake_module")
defaults = next(ast_nodes[4].infer())
- self.assertIsInstance(defaults, astroid.Tuple)
+ self.assertIsInstance(defaults, nodes.Tuple)
self.assertEqual([default.value for default in defaults.elts], [1, 2])
dict_ = next(ast_nodes[5].infer())
- self.assertIsInstance(dict_, astroid.Dict)
+ self.assertIsInstance(dict_, nodes.Dict)
globals_ = next(ast_nodes[6].infer())
- self.assertIsInstance(globals_, astroid.Dict)
+ self.assertIsInstance(globals_, nodes.Dict)
for ast_node in ast_nodes[7:9]:
self.assertIs(next(ast_node.infer()), astroid.Uninferable)
@@ -511,81 +502,66 @@
new_ = next(ast_nodes[10].infer())
assert isinstance(new_, bases.BoundMethod)
- # The following nodes are just here for theoretical completeness,
- # and they either return Uninferable or raise InferenceError.
- for ast_node in ast_nodes[11:26]:
+ # Remaining attributes return Uninferable.
+ for ast_node in ast_nodes[11:34]:
inferred = next(ast_node.infer())
assert inferred is util.Uninferable
- for ast_node in ast_nodes[26:27]:
- with pytest.raises(InferenceError):
- inferred = next(ast_node.infer())
-
def test_empty_return_annotation(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
def test(): pass
test.__annotations__
- """
- )
+ """)
annotations = next(ast_node.infer())
- self.assertIsInstance(annotations, astroid.Dict)
+ self.assertIsInstance(annotations, nodes.Dict)
self.assertEqual(len(annotations.items), 0)
def test_builtin_dunder_init_does_not_crash_when_accessing_annotations(
self,
) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
class Class:
@classmethod
def class_method(cls):
cls.__init__.__annotations__ #@
- """
- )
+ """)
inferred = next(ast_node.infer())
- self.assertIsInstance(inferred, astroid.Dict)
+ self.assertIsInstance(inferred, nodes.Dict)
self.assertEqual(len(inferred.items), 0)
def test_annotations_kwdefaults(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
def test(a: 1, *args: 2, f:4='lala', **kwarg:3)->2: pass
test.__annotations__ #@
test.__kwdefaults__ #@
- """
- )
+ """)
annotations = next(ast_node[0].infer())
- self.assertIsInstance(annotations, astroid.Dict)
- self.assertIsInstance(
- annotations.getitem(astroid.Const("return")), astroid.Const
- )
- self.assertEqual(annotations.getitem(astroid.Const("return")).value, 2)
- self.assertIsInstance(annotations.getitem(astroid.Const("a")), astroid.Const)
- self.assertEqual(annotations.getitem(astroid.Const("a")).value, 1)
- self.assertEqual(annotations.getitem(astroid.Const("args")).value, 2)
- self.assertEqual(annotations.getitem(astroid.Const("kwarg")).value, 3)
+ self.assertIsInstance(annotations, nodes.Dict)
+ self.assertIsInstance(annotations.getitem(nodes.Const("return")), nodes.Const)
+ self.assertEqual(annotations.getitem(nodes.Const("return")).value, 2)
+ self.assertIsInstance(annotations.getitem(nodes.Const("a")), nodes.Const)
+ self.assertEqual(annotations.getitem(nodes.Const("a")).value, 1)
+ self.assertEqual(annotations.getitem(nodes.Const("args")).value, 2)
+ self.assertEqual(annotations.getitem(nodes.Const("kwarg")).value, 3)
- self.assertEqual(annotations.getitem(astroid.Const("f")).value, 4)
+ self.assertEqual(annotations.getitem(nodes.Const("f")).value, 4)
kwdefaults = next(ast_node[1].infer())
- self.assertIsInstance(kwdefaults, astroid.Dict)
+ self.assertIsInstance(kwdefaults, nodes.Dict)
# self.assertEqual(kwdefaults.getitem('f').value, 'lala')
def test_annotation_positional_only(self):
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
def test(a: 1, b: 2, /, c: 3): pass
test.__annotations__ #@
- """
- )
+ """)
annotations = next(ast_node.infer())
- self.assertIsInstance(annotations, astroid.Dict)
+ self.assertIsInstance(annotations, nodes.Dict)
- self.assertIsInstance(annotations.getitem(astroid.Const("a")), astroid.Const)
- self.assertEqual(annotations.getitem(astroid.Const("a")).value, 1)
- self.assertEqual(annotations.getitem(astroid.Const("b")).value, 2)
- self.assertEqual(annotations.getitem(astroid.Const("c")).value, 3)
+ self.assertIsInstance(annotations.getitem(nodes.Const("a")), nodes.Const)
+ self.assertEqual(annotations.getitem(nodes.Const("a")).value, 1)
+ self.assertEqual(annotations.getitem(nodes.Const("b")).value, 2)
+ self.assertEqual(annotations.getitem(nodes.Const("c")).value, 3)
def test_is_not_lambda(self):
ast_node = builder.extract_node("def func(): pass")
@@ -595,8 +571,7 @@
class TestContextManagerModel:
def test_model(self) -> None:
"""We use a generator to test this model."""
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
def test():
"a"
yield
@@ -604,8 +579,7 @@
gen = test()
gen.__enter__ #@
gen.__exit__ #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
enter = next(ast_nodes[0].infer())
@@ -633,8 +607,7 @@
class GeneratorModelTest(unittest.TestCase):
def test_model(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
def test():
"a"
yield
@@ -647,8 +620,7 @@
gen.send #@
gen.__enter__ #@
gen.__exit__ #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
name = next(ast_nodes[0].infer())
self.assertEqual(name.value, "test")
@@ -657,11 +629,11 @@
self.assertEqual(doc.value, "a")
gi_code = next(ast_nodes[2].infer())
- self.assertIsInstance(gi_code, astroid.ClassDef)
+ self.assertIsInstance(gi_code, nodes.ClassDef)
self.assertEqual(gi_code.name, "gi_code")
gi_frame = next(ast_nodes[3].infer())
- self.assertIsInstance(gi_frame, astroid.ClassDef)
+ self.assertIsInstance(gi_frame, nodes.ClassDef)
self.assertEqual(gi_frame.name, "gi_frame")
send = next(ast_nodes[4].infer())
@@ -677,8 +649,7 @@
class ExceptionModelTest(unittest.TestCase):
@staticmethod
def test_valueerror_py3() -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
try:
x[42]
except ValueError as err:
@@ -686,11 +657,10 @@
err.__traceback__ #@
err.message #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
args = next(ast_nodes[0].infer())
- assert isinstance(args, astroid.Tuple)
+ assert isinstance(args, nodes.Tuple)
tb = next(ast_nodes[1].infer())
# Python 3.11: If 'contextlib' is loaded, '__traceback__'
# could be set inside '__exit__' method in
@@ -708,33 +678,29 @@
next(ast_nodes[2].infer())
def test_syntax_error(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
try:
x[42]
except SyntaxError as err:
err.text #@
- """
- )
+ """)
inferred = next(ast_node.infer())
- assert isinstance(inferred, astroid.Const)
+ assert isinstance(inferred, nodes.Const)
@unittest.skipIf(HAS_SIX, "This test fails if the six library is installed")
def test_oserror(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
try:
raise OSError("a")
except OSError as err:
err.filename #@
err.filename2 #@
err.errno #@
- """
- )
+ """)
expected_values = ["", "", 0]
for node, value in zip(ast_nodes, expected_values):
inferred = next(node.infer())
- assert isinstance(inferred, astroid.Const)
+ assert isinstance(inferred, nodes.Const)
assert inferred.value == value
def test_unicodedecodeerror(self) -> None:
@@ -746,66 +712,58 @@
"""
node = builder.extract_node(code)
inferred = next(node.infer())
- assert isinstance(inferred, astroid.Const)
+ assert isinstance(inferred, nodes.Const)
assert inferred.value == b""
def test_import_error(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
try:
raise ImportError("a")
except ImportError as err:
err.name #@
err.path #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
- assert isinstance(inferred, astroid.Const)
+ assert isinstance(inferred, nodes.Const)
assert inferred.value == ""
def test_exception_instance_correctly_instantiated(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
try:
raise ImportError("a")
except ImportError as err:
err #@
- """
- )
+ """)
inferred = next(ast_node.infer())
assert isinstance(inferred, astroid.Instance)
cls = next(inferred.igetattr("__class__"))
- assert isinstance(cls, astroid.ClassDef)
+ assert isinstance(cls, nodes.ClassDef)
class DictObjectModelTest(unittest.TestCase):
def test__class__(self) -> None:
ast_node = builder.extract_node("{}.__class__")
inferred = next(ast_node.infer())
- self.assertIsInstance(inferred, astroid.ClassDef)
+ self.assertIsInstance(inferred, nodes.ClassDef)
self.assertEqual(inferred.name, "dict")
def test_attributes_inferred_as_methods(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
{}.values #@
{}.items #@
{}.keys #@
- """
- )
+ """)
for node in ast_nodes:
inferred = next(node.infer())
self.assertIsInstance(inferred, astroid.BoundMethod)
def test_wrapper_objects_for_dict_methods_python3(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
{1:1, 2:3}.values() #@
{1:1, 2:3}.keys() #@
{1:1, 2:3}.items() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
values = next(ast_nodes[0].infer())
self.assertIsInstance(values, objects.DictValues)
@@ -822,11 +780,9 @@
def test_str_argument_not_required(self) -> None:
"""Test that the first argument to an exception does not need to be a str."""
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
BaseException() #@
- """
- )
+ """)
args: nodes.Tuple = next(ast_node.infer()).getattr("args")[0]
# BaseException doesn't have any required args, not even a string
assert not args.elts
@@ -834,8 +790,7 @@
@pytest.mark.parametrize("parentheses", (True, False))
def test_lru_cache(parentheses) -> None:
- ast_nodes = builder.extract_node(
- f"""
+ ast_nodes = builder.extract_node(f"""
import functools
class Foo(object):
@functools.lru_cache{"()" if parentheses else ""}
@@ -845,13 +800,12 @@
f.foo.cache_clear #@
f.foo.__wrapped__ #@
f.foo.cache_info() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
cache_clear = next(ast_nodes[0].infer())
assert isinstance(cache_clear, astroid.BoundMethod)
wrapped = next(ast_nodes[1].infer())
- assert isinstance(wrapped, astroid.FunctionDef)
+ assert isinstance(wrapped, nodes.FunctionDef)
assert wrapped.name == "foo"
cache_info = next(ast_nodes[2].infer())
assert isinstance(cache_info, astroid.Instance)
@@ -859,13 +813,11 @@
def test_class_annotations() -> None:
"""Test that the `__annotations__` attribute is avaiable in the class scope"""
- annotations, klass_attribute = builder.extract_node(
- """
+ annotations, klass_attribute = builder.extract_node("""
class Test:
__annotations__ #@
Test.__annotations__ #@
- """
- )
+ """)
# Test that `__annotations__` attribute is available in the class scope:
assert isinstance(annotations, nodes.Name)
# The `__annotations__` attribute is `Uninferable`:
@@ -877,8 +829,7 @@
def test_class_annotations_typed_dict() -> None:
"""Test that we can access class annotations on various TypedDicts"""
- apple, pear = builder.extract_node(
- """
+ apple, pear = builder.extract_node("""
from typing import TypedDict
@@ -892,10 +843,176 @@
Apple.__annotations__ #@
Pear.__annotations__ #@
- """
- )
+ """)
assert isinstance(apple, nodes.Attribute)
assert next(apple.infer()) is astroid.Uninferable
assert isinstance(pear, nodes.Attribute)
assert next(pear.infer()) is astroid.Uninferable
+
+
+def test_object_dunder_methods_can_be_overridden() -> None:
+ """Test that ObjectModel dunders don't block class overrides."""
+ # Test instance method override
+ eq_result = builder.extract_node("""
+ class MyClass:
+ def __eq__(self, other):
+ return "custom equality"
+
+ MyClass().__eq__(None) #@
+ """)
+ inferred = next(eq_result.infer())
+ assert isinstance(inferred, nodes.Const)
+ assert inferred.value == "custom equality"
+
+ # Test that __eq__ on instance returns a bound method
+ eq_method = builder.extract_node("""
+ class MyClass:
+ def __eq__(self, other):
+ return True
+
+ MyClass().__eq__ #@
+ """)
+ inferred = next(eq_method.infer())
+ assert isinstance(inferred, astroid.BoundMethod)
+
+ # Test other commonly overridden dunders
+ for dunder, return_val in (
+ ("__ne__", "not equal"),
+ ("__lt__", "less than"),
+ ("__le__", "less or equal"),
+ ("__gt__", "greater than"),
+ ("__ge__", "greater or equal"),
+ ("__str__", "string repr"),
+ ("__repr__", "repr"),
+ ("__hash__", 42),
+ ):
+ node = builder.extract_node(f"""
+ class MyClass:
+ def {dunder}(self, *args):
+ return {return_val!r}
+
+ MyClass().{dunder}() #@
+ """)
+ inferred = next(node.infer())
+ assert isinstance(inferred, nodes.Const), f"{dunder} failed to infer correctly"
+ assert inferred.value == return_val, f"{dunder} returned wrong value"
+
+
+def test_unoverridden_object_dunders_return_uninferable() -> None:
+ """Test that un-overridden object dunders return Uninferable when called."""
+ for dunder in (
+ "__eq__",
+ "__hash__",
+ "__lt__",
+ "__le__",
+ "__gt__",
+ "__ge__",
+ "__ne__",
+ ):
+ node = builder.extract_node(f"""
+ class MyClass:
+ pass
+
+ MyClass().{dunder}(None) if "{dunder}" != "__hash__" else MyClass().{dunder}() #@
+ """)
+ result = next(node.infer())
+ assert result is util.Uninferable
+
+
+def test_all_object_dunders_accessible() -> None:
+ """Test that object dunders are accessible on classes and instances."""
+ # Use actual dunders from object in the current Python version
+ object_dunders = [attr for attr in dir(object) if attr.startswith("__")]
+
+ cls, instance = builder.extract_node("""
+ class MyClass:
+ pass
+
+ MyClass #@
+ MyClass() #@
+ """)
+ cls = next(cls.infer())
+ instance = next(instance.infer())
+
+ for dunder in object_dunders:
+ assert cls.getattr(dunder)
+ assert instance.getattr(dunder)
+
+
+def test_hash_none_for_unhashable_builtins() -> None:
+ """Test that unhashable builtin types have __hash__ = None."""
+ for type_name in ("list", "dict", "set"):
+ node = builder.extract_node(f"{type_name} #@")
+ cls = next(node.infer())
+ hash_attr = cls.getattr("__hash__")[0]
+ assert isinstance(hash_attr, nodes.Const)
+ assert hash_attr.value is None
+
+
+def test_unbound_method_object_dunders_return_uninferable() -> None:
+ """Test that ObjectModel-only dunders on unbound methods return Uninferable."""
+ node = builder.extract_node("""
+ class A:
+ def test(self): pass
+ A.test #@
+ """)
+ unbound = next(node.infer())
+ assert isinstance(unbound, bases.UnboundMethod)
+
+ for dunder in (
+ "__eq__",
+ "__ne__",
+ "__lt__",
+ "__le__",
+ "__gt__",
+ "__ge__",
+ "__repr__",
+ "__str__",
+ "__hash__",
+ ):
+ inferred = next(unbound.igetattr(dunder))
+ assert inferred is util.Uninferable, f"{dunder} should be Uninferable"
+
+ attrs = unbound.getattr(dunder)
+ assert attrs, f"{dunder} getattr should return results"
+
+
+def test_super_special_attributes_fallback() -> None:
+ """Test that super() special attributes use the fallback path correctly."""
+ doc_node = builder.extract_node("""
+ class Base:
+ def method(self):
+ return super().__doc__
+ Base().method() #@
+ """)
+ doc_inferred = next(doc_node.infer())
+ assert doc_inferred is util.Uninferable
+
+ thisclass_node = builder.extract_node("""
+ class Base:
+ def method(self):
+ return super().__thisclass__
+ Base().method() #@
+ """)
+ thisclass_inferred = next(thisclass_node.infer())
+ assert isinstance(thisclass_inferred, nodes.ClassDef)
+ assert thisclass_inferred.name == "Base"
+
+
+@pytest.mark.parametrize(
+ "attr",
+ [
+ "__get__",
+ "__defaults__",
+ "__annotations__",
+ "__dict__",
+ "__globals__",
+ "__kwdefaults__",
+ ],
+)
+def test_builtin_func_no_descriptor_attrs(attr: str) -> None:
+ """Test builtin functions lack descriptor protocol attributes."""
+ node = builder.extract_node(f"eval.{attr}")
+ with pytest.raises(InferenceError):
+ next(node.infer())
diff --git a/tests/test_objects.py b/tests/test_objects.py
index e9e8726..a13c6f4 100644
--- a/tests/test_objects.py
+++ b/tests/test_objects.py
@@ -13,11 +13,9 @@
class ObjectsTest(unittest.TestCase):
def test_frozenset(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
frozenset({1: 2, 2: 3}) #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, objects.FrozenSet)
@@ -39,22 +37,19 @@
rely on the ._proxied attribute of an Instance.
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class ClassHavingUnknownAncestors(Unknown):
__slots__ = ["yo"]
def test(self):
self.not_yo = 42
- """
- )
+ """)
assert node.getattr("__new__")
class SuperTests(unittest.TestCase):
def test_inferring_super_outside_methods(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class Module(object):
pass
class StaticMethod(object):
@@ -66,8 +61,7 @@
super(Module, Module) #@
# no argument super is not recognised outside methods as well.
super() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
in_static = next(ast_nodes[0].value.infer())
self.assertIsInstance(in_static, bases.Instance)
@@ -82,26 +76,22 @@
self.assertEqual(no_arguments.qname(), "builtins.super")
def test_inferring_unbound_super_doesnt_work(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class Test(object):
def __init__(self):
super(Test) #@
- """
- )
+ """)
unbounded = next(node.infer())
self.assertIsInstance(unbounded, bases.Instance)
self.assertEqual(unbounded.qname(), "builtins.super")
def test_use_default_inference_on_not_inferring_args(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class Test(object):
def __init__(self):
super(Lala, self) #@
super(Test, lala) #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, bases.Instance)
@@ -112,8 +102,7 @@
self.assertEqual(second.qname(), "builtins.super")
def test_no_arguments_super(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class First(object): pass
class Second(First):
def test(self):
@@ -121,8 +110,7 @@
@classmethod
def test_classmethod(cls):
super() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, objects.Super)
@@ -139,8 +127,7 @@
self.assertEqual(second.mro_pointer.name, "Second")
def test_super_simple_cases(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class First(object): pass
class Second(First): pass
class Third(First):
@@ -155,8 +142,7 @@
class Fourth(Third):
pass
- """
- )
+ """)
# .type is the object which provides the mro.
# .mro_pointer is the position in the mro from where
@@ -204,13 +190,11 @@
self.assertEqual(fifth.mro_pointer.name, "Fourth")
def test_super_infer(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class Super(object):
def __init__(self):
super(Super, self) #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, objects.Super)
reinferred = next(inferred.infer())
@@ -218,8 +202,7 @@
self.assertIs(inferred, reinferred)
def test_inferring_invalid_supers(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class Super(object):
def __init__(self):
# MRO pointer is not a type
@@ -230,8 +213,7 @@
super(Bupper, self) #@
class Bupper(Super):
pass
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, objects.Super)
@@ -247,21 +229,18 @@
self.assertIsInstance(cm.exception.super_.type, invalid_type)
def test_proxied(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class Super(object):
def __init__(self):
super(Super, self) #@
- """
- )
+ """)
inferred = next(node.infer())
proxied = inferred._proxied
self.assertEqual(proxied.qname(), "builtins.super")
self.assertIsInstance(proxied, nodes.ClassDef)
def test_super_bound_model(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class First(object):
def method(self):
pass
@@ -281,8 +260,7 @@
def method(self):
super(Super_Type_Object, self).method #@
super(Super_Type_Object, self).class_method #@
- """
- )
+ """)
# Super(type, type) is the same for both functions and classmethods.
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
@@ -316,8 +294,7 @@
self.assertEqual(sixth.type, "classmethod")
def test_super_getattr_single_inheritance(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class First(object):
def test(self): pass
class Second(First):
@@ -335,8 +312,7 @@
super(Third, Third).test2 #@
super(Third, Third).test #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, bases.BoundMethod)
@@ -365,22 +341,19 @@
self.assertEqual(second_unbound.parent.name, "First")
def test_super_invalid_mro(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A(object):
test = 42
class Super(A, A):
def __init__(self):
super(Super, self) #@
- """
- )
+ """)
inferred = next(node.infer())
with self.assertRaises(AttributeInferenceError):
next(inferred.getattr("test"))
def test_super_complex_mro(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class A(object):
def spam(self): return "A"
def foo(self): return "A"
@@ -398,8 +371,7 @@
super(E, self).spam #@
super(E, self).foo #@
super(E, self).static #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, bases.BoundMethod)
@@ -417,16 +389,14 @@
self.assertEqual(static.parent.scope().name, "A")
def test_super_data_model(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class X(object): pass
class A(X):
def __init__(self):
super(A, self) #@
super(A, A) #@
super(X, A) #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
thisclass = first.getattr("__thisclass__")[0]
@@ -458,8 +428,7 @@
self.assertEqual([member.name for member in klass.super_mro()], expected_mro)
def test_super_mro(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class A(object): pass
class B(A): pass
class C(A): pass
@@ -471,8 +440,7 @@
super(B, 1) #@
super(1, B) #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertEqualMro(first, ["C", "B", "A", "object"])
@@ -489,30 +457,76 @@
fifth.super_mro()
def test_super_yes_objects(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
from collections import Missing
class A(object):
def __init__(self):
super(Missing, self) #@
super(A, Missing) #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
first = next(ast_nodes[0].infer())
self.assertIsInstance(first, bases.Instance)
second = next(ast_nodes[1].infer())
self.assertIsInstance(second, bases.Instance)
+ def test_super_method_chaining_return_type(self) -> None:
+ """Test that super().method() correctly infers child type.
+
+ Regression test for: https://github.com/pylint-dev/astroid/issues/457
+ """
+ node = builder.extract_node("""
+ class Parent:
+ def method(self) -> "Parent":
+ return self
+
+ class Child(Parent):
+ def method(self) -> "Child":
+ return super().method()
+
+ def extra(self) -> "Child":
+ return self
+
+ Child().method().extra() #@
+ """)
+ inferred = next(node.infer())
+ # Should infer Child instance, not Parent
+ self.assertIsInstance(inferred, bases.Instance)
+ self.assertEqual(inferred._proxied.name, "Child")
+
+ def test_super_classmethod_chaining_return_type(self) -> None:
+ """Test that super().classmethod() correctly infers child type.
+
+ Regression test for: https://github.com/pylint-dev/astroid/issues/457
+ """
+ node = builder.extract_node("""
+ class Parent:
+ @classmethod
+ def create(cls) -> "Parent":
+ return cls()
+
+ class Child(Parent):
+ @classmethod
+ def create(cls) -> "Child":
+ return super().create()
+
+ def extra(self) -> "Child":
+ return self
+
+ Child.create().extra() #@
+ """)
+ inferred = next(node.infer())
+ # Should infer Child instance, not Parent
+ self.assertIsInstance(inferred, bases.Instance)
+ self.assertEqual(inferred._proxied.name, "Child")
+
def test_super_invalid_types(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
import collections
class A(object):
def __init__(self):
super(A, collections) #@
- """
- )
+ """)
inferred = next(node.infer())
with self.assertRaises(SuperError):
inferred.super_mro()
@@ -520,8 +534,7 @@
inferred.super_mro()
def test_super_properties(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class Foo(object):
@property
def dict(self):
@@ -533,8 +546,7 @@
return super(Bar, self).dict
Bar().dict
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Const)
self.assertEqual(inferred.value, 42)
@@ -554,15 +566,13 @@
def test_super_new_call(self) -> None:
"""Test that __new__ returns an object or node and not a (Un)BoundMethod."""
- new_call_result: nodes.Name = builder.extract_node(
- """
+ new_call_result: nodes.Name = builder.extract_node("""
import enum
class ChoicesMeta(enum.EnumMeta):
def __new__(metacls, classname, bases, classdict, **kwds):
cls = super().__new__(metacls, "str", (enum.Enum,), enum._EnumDict(), **kwargs)
cls #@
- """
- )
+ """)
inferred = list(new_call_result.infer())
assert all(
isinstance(i, (nodes.NodeNG, type(util.Uninferable))) for i in inferred
@@ -570,8 +580,7 @@
def test_super_init_call(self) -> None:
"""Test that __init__ is still callable."""
- init_node: nodes.Attribute = builder.extract_node(
- """
+ init_node: nodes.Attribute = builder.extract_node("""
class SuperUsingClass:
@staticmethod
def test():
@@ -581,8 +590,7 @@
pass
A().__new__ #@
A().__init__ #@
- """
- )
+ """)
assert isinstance(next(init_node[0].infer()), bases.BoundMethod)
assert isinstance(next(init_node[1].infer()), bases.BoundMethod)
assert isinstance(next(init_node[2].infer()), bases.BoundMethod)
diff --git a/tests/test_protocols.py b/tests/test_protocols.py
index 4a9f1f6..4987d2a 100644
--- a/tests/test_protocols.py
+++ b/tests/test_protocols.py
@@ -13,7 +13,7 @@
import astroid
from astroid import extract_node, nodes
-from astroid.const import PY310_PLUS, PY312_PLUS
+from astroid.const import PY312_PLUS
from astroid.exceptions import InferenceError
from astroid.manager import AstroidManager
from astroid.util import Uninferable, UninferableBase
@@ -53,15 +53,13 @@
self.assertEqual(expected_name, node.name)
def test_assigned_stmts_simple_for(self) -> None:
- assign_stmts = extract_node(
- """
+ assign_stmts = extract_node("""
for a in (1, 2, 3): #@
pass
for b in range(3): #@
pass
- """
- )
+ """)
for1_assnode = next(assign_stmts[0].nodes_of_class(nodes.AssignName))
assigned = list(for1_assnode.assigned_stmts())
@@ -71,12 +69,10 @@
self.assertRaises(InferenceError, list, for2_assnode.assigned_stmts())
def test_assigned_stmts_nested_for_tuple(self) -> None:
- assign_stmts = extract_node(
- """
+ assign_stmts = extract_node("""
for a, (b, c) in [(1, (2, 3))]: #@
pass
- """
- )
+ """)
assign_nodes = assign_stmts.nodes_of_class(nodes.AssignName)
@@ -89,12 +85,10 @@
self.assertConstNodesEqual([2], assigned2)
def test_assigned_stmts_nested_for_dict(self) -> None:
- assign_stmts = extract_node(
- """
+ assign_stmts = extract_node("""
for a, (b, c) in {1: ("a", str), 2: ("b", bytes)}.items(): #@
pass
- """
- )
+ """)
assign_nodes = assign_stmts.nodes_of_class(nodes.AssignName)
# assigned: [1, 2]
@@ -113,16 +107,14 @@
self.assertNameNodesEqual(["str", "bytes"], assigned3)
def test_assigned_stmts_starred_for(self) -> None:
- assign_stmts = extract_node(
- """
+ assign_stmts = extract_node("""
for *a, b in ((1, 2, 3), (4, 5, 6, 7)): #@
pass
- """
- )
+ """)
for1_starred = next(assign_stmts.nodes_of_class(nodes.Starred))
assigned = next(for1_starred.assigned_stmts())
- assert isinstance(assigned, astroid.List)
+ assert isinstance(assigned, nodes.List)
assert assigned.as_string() == "[1, 2]"
def _get_starred_stmts(self, code: str) -> list | UninferableBase:
@@ -191,13 +183,11 @@
)
def test_assigned_stmts_assignments(self) -> None:
- assign_stmts = extract_node(
- """
+ assign_stmts = extract_node("""
c = a #@
d, e = b, c #@
- """
- )
+ """)
simple_assnode = next(assign_stmts[0].nodes_of_class(nodes.AssignName))
assigned = list(simple_assnode.assigned_stmts())
@@ -212,12 +202,10 @@
self.assertNameNodesEqual(["c"], assigned)
def test_assigned_stmts_annassignments(self) -> None:
- annassign_stmts = extract_node(
- """
+ annassign_stmts = extract_node("""
a: str = "abc" #@
b: str #@
- """
- )
+ """)
simple_annassign_node = next(
annassign_stmts[0].nodes_of_class(nodes.AssignName)
)
@@ -236,12 +224,10 @@
node.root().locals["__all__"] = [node.value]
manager = astroid.MANAGER
- with _add_transform(manager, astroid.Assign, transform):
- module = astroid.parse(
- """
+ with _add_transform(manager, nodes.Assign, transform):
+ module = astroid.parse("""
__all__ = ['a']
- """
- )
+ """)
module.wildcard_import_names()
def test_not_passing_uninferable_in_seq_inference(self) -> None:
@@ -261,12 +247,10 @@
for _ in node.infer():
pass
- parsed = extract_node(
- """
+ parsed = extract_node("""
a = []
x = [a*2, a]*2*2
- """
- )
+ """)
parsed.accept(Visitor())
@staticmethod
@@ -364,7 +348,6 @@
assert node.value == 1
-@pytest.mark.skipif(not PY310_PLUS, reason="Match requires python 3.10")
class TestPatternMatching:
@staticmethod
def test_assigned_stmts_match_mapping():
@@ -372,14 +355,12 @@
Test the result is 'Uninferable' and no exception is raised.
"""
- assign_stmts = extract_node(
- """
+ assign_stmts = extract_node("""
var = {1: "Hello", 2: "World"}
match var:
case {**rest}: #@
pass
- """
- )
+ """)
match_mapping: nodes.MatchMapping = assign_stmts.pattern # type: ignore[union-attr]
assert match_mapping.rest
assigned = next(match_mapping.rest.assigned_stmts())
@@ -391,14 +372,12 @@
Test the result is 'Uninferable' and no exception is raised.
"""
- assign_stmts = extract_node(
- """
+ assign_stmts = extract_node("""
var = (0, 1, 2)
match var:
case (0, 1, *rest): #@
pass
- """
- )
+ """)
match_sequence: nodes.MatchSequence = assign_stmts.pattern # type: ignore[union-attr]
match_star = match_sequence.patterns[2]
assert isinstance(match_star, nodes.MatchStar) and match_star.name
@@ -408,8 +387,7 @@
@staticmethod
def test_assigned_stmts_match_as():
"""Assigned_stmts for MatchAs only implemented for the most basic case (y)."""
- assign_stmts = extract_node(
- """
+ assign_stmts = extract_node("""
var = 42
match var: #@
case 2 | x: #@
@@ -418,8 +396,7 @@
pass
case z: #@
pass
- """
- )
+ """)
subject: nodes.Const = assign_stmts[0].subject # type: ignore[index,union-attr]
match_or: nodes.MatchOr = assign_stmts[1].pattern # type: ignore[index,union-attr]
match_as_with_pattern: nodes.MatchAs = assign_stmts[2].pattern # type: ignore[index,union-attr]
diff --git a/tests/test_python3.py b/tests/test_python3.py
index d982e6f..89d53ae 100644
--- a/tests/test_python3.py
+++ b/tests/test_python3.py
@@ -26,12 +26,10 @@
self.assertTrue(isinstance(node.assign_type(), nodes.Assign))
def test_yield_from(self) -> None:
- body = dedent(
- """
+ body = dedent("""
def func():
yield from iter([1, 2])
- """
- )
+ """)
astroid = self.builder.string_build(body)
func = astroid.body[0]
self.assertIsInstance(func, nodes.FunctionDef)
@@ -42,25 +40,21 @@
self.assertEqual(yieldfrom_stmt.as_string(), "yield from iter([1, 2])")
def test_yield_from_is_generator(self) -> None:
- body = dedent(
- """
+ body = dedent("""
def func():
yield from iter([1, 2])
- """
- )
+ """)
astroid = self.builder.string_build(body)
func = astroid.body[0]
self.assertIsInstance(func, nodes.FunctionDef)
self.assertTrue(func.is_generator())
def test_yield_from_as_string(self) -> None:
- body = dedent(
- """
+ body = dedent("""
def func():
yield from iter([1, 2])
value = yield from other()
- """
- )
+ """)
astroid = self.builder.string_build(body)
func = astroid.body[0]
self.assertEqual(func.as_string().strip(), body.strip())
@@ -81,13 +75,9 @@
self.assertFalse(klass.metaclass())
def test_metaclass_imported(self) -> None:
- astroid = self.builder.string_build(
- dedent(
- """
+ astroid = self.builder.string_build(dedent("""
from abc import ABCMeta
- class Test(metaclass=ABCMeta): pass"""
- )
- )
+ class Test(metaclass=ABCMeta): pass"""))
klass = astroid.body[1]
metaclass = klass.metaclass()
@@ -105,11 +95,9 @@
self.assertEqual(metaclass.name, "type")
def test_as_string(self) -> None:
- body = dedent(
- """
+ body = dedent("""
from abc import ABCMeta
- class Test(metaclass=ABCMeta): pass"""
- )
+ class Test(metaclass=ABCMeta): pass""")
astroid = self.builder.string_build(body)
klass = astroid.body[1]
@@ -118,52 +106,38 @@
)
def test_old_syntax_works(self) -> None:
- astroid = self.builder.string_build(
- dedent(
- """
+ astroid = self.builder.string_build(dedent("""
class Test:
__metaclass__ = type
class SubTest(Test): pass
- """
- )
- )
+ """))
klass = astroid["SubTest"]
metaclass = klass.metaclass()
self.assertIsNone(metaclass)
def test_metaclass_yes_leak(self) -> None:
- astroid = self.builder.string_build(
- dedent(
- """
+ astroid = self.builder.string_build(dedent("""
# notice `ab` instead of `abc`
from ab import ABCMeta
class Meta(metaclass=ABCMeta): pass
- """
- )
- )
+ """))
klass = astroid["Meta"]
self.assertIsNone(klass.metaclass())
def test_parent_metaclass(self) -> None:
- astroid = self.builder.string_build(
- dedent(
- """
+ astroid = self.builder.string_build(dedent("""
from abc import ABCMeta
class Test(metaclass=ABCMeta): pass
class SubTest(Test): pass
- """
- )
- )
+ """))
klass = astroid["SubTest"]
metaclass = klass.metaclass()
self.assertIsInstance(metaclass, nodes.ClassDef)
self.assertEqual(metaclass.name, "ABCMeta")
def test_metaclass_ancestors(self) -> None:
- astroid = self.builder.string_build(
- dedent(
- """
+ astroid = self.builder.string_build(dedent("""
from abc import ABCMeta
class FirstMeta(metaclass=ABCMeta): pass
@@ -177,9 +151,7 @@
class SecondImpl(FirstImpl): pass
class ThirdImpl(Simple, SecondMeta):
pass
- """
- )
- )
+ """))
classes = {"ABCMeta": ("FirstImpl", "SecondImpl"), "type": ("ThirdImpl",)}
for metaclass, names in classes.items():
for name in names:
@@ -189,15 +161,11 @@
self.assertEqual(meta.name, metaclass)
def test_annotation_support(self) -> None:
- astroid = self.builder.string_build(
- dedent(
- """
+ astroid = self.builder.string_build(dedent("""
def test(a: int, b: str, c: None, d, e,
*args: float, **kwargs: int)->int:
pass
- """
- )
- )
+ """))
func = astroid["test"]
self.assertIsInstance(func.args.varargannotation, nodes.Name)
self.assertEqual(func.args.varargannotation.name, "float")
@@ -215,14 +183,10 @@
self.assertIsNone(arguments.annotations[3])
self.assertIsNone(arguments.annotations[4])
- astroid = self.builder.string_build(
- dedent(
- """
+ astroid = self.builder.string_build(dedent("""
def test(a: int=1, b: str=2):
pass
- """
- )
- )
+ """))
func = astroid["test"]
self.assertIsInstance(func.args.annotations[0], nodes.Name)
self.assertEqual(func.args.annotations[0].name, "int")
@@ -231,14 +195,10 @@
self.assertIsNone(func.returns)
def test_kwonlyargs_annotations_supper(self) -> None:
- node = self.builder.string_build(
- dedent(
- """
+ node = self.builder.string_build(dedent("""
def test(*, a: int, b: str, c: None, d, e):
pass
- """
- )
- )
+ """))
func = node["test"]
arguments = func.args
self.assertIsInstance(arguments.kwonlyargs_annotations[0], nodes.Name)
@@ -251,16 +211,12 @@
self.assertIsNone(arguments.kwonlyargs_annotations[4])
def test_annotation_as_string(self) -> None:
- code1 = dedent(
- """
+ code1 = dedent("""
def test(a, b: int = 4, c=2, f: 'lala' = 4) -> 2:
- pass"""
- )
- code2 = dedent(
- """
+ pass""")
+ code2 = dedent("""
def test(a: typing.Generic[T], c: typing.Any = 24) -> typing.Iterable:
- pass"""
- )
+ pass""")
for code in (code1, code2):
func = extract_node(code)
self.assertEqual(func.as_string(), code)
@@ -288,12 +244,10 @@
@staticmethod
def test_unpacking_in_dict_getitem_with_ref() -> None:
- node = extract_node(
- """
+ node = extract_node("""
a = {1: 2}
{**a, 2: 3} #@
- """
- )
+ """)
assert isinstance(node, nodes.Dict)
for key, expected in ((1, 2), (2, 3)):
@@ -390,10 +344,8 @@
"return {fun: await fun() async for fun in funcs if await smth}",
]
for func_body in func_bodies:
- code = dedent(
- f"""
+ code = dedent(f"""
async def f():
- {func_body}"""
- )
+ {func_body}""")
func = extract_node(code)
self.assertEqual(func.as_string().strip(), code.strip())
diff --git a/tests/test_raw_building.py b/tests/test_raw_building.py
index bb7c733..fdd8603 100644
--- a/tests/test_raw_building.py
+++ b/tests/test_raw_building.py
@@ -10,7 +10,7 @@
from __future__ import annotations
-import _io
+import _io # pylint: disable=wrong-import-order
import logging
import os
import sys
@@ -19,7 +19,6 @@
from typing import Any
from unittest import mock
-import mypy.build
import pytest
import tests.testdata.python3.data.fake_module_with_broken_getattr as fm_getattr
@@ -37,6 +36,13 @@
object_build_class,
)
+try:
+ import mypy.build
+
+ HAS_MYPY = True
+except ImportError:
+ HAS_MYPY = False
+
DUMMY_MOD = build_module("DUMMY")
@@ -173,6 +179,7 @@
assert not err
+@pytest.mark.skipif(not HAS_MYPY, reason="This test requires mypy")
def test_missing__dict__():
# This shouldn't raise an exception.
object_build_class(DUMMY_MOD, mypy.build.ModuleNotFound)
diff --git a/tests/test_regrtest.py b/tests/test_regrtest.py
index 76a7cea..a207057 100644
--- a/tests/test_regrtest.py
+++ b/tests/test_regrtest.py
@@ -2,6 +2,7 @@
# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
+import platform
import sys
import textwrap
import unittest
@@ -13,7 +14,7 @@
from astroid.builder import AstroidBuilder, _extract_single_node, extract_node
from astroid.const import PY312_PLUS
from astroid.context import InferenceContext
-from astroid.exceptions import InferenceError
+from astroid.exceptions import AstroidSyntaxError, InferenceError
from astroid.manager import AstroidManager
from astroid.raw_building import build_module
from astroid.util import Uninferable
@@ -110,12 +111,10 @@
PY312_PLUS -- This test will likely become unnecessary when Python 3.12 is
numpy's minimum version. (numpy.distutils will be removed then.)
"""
- node = extract_node(
- """
+ node = extract_node("""
from numpy.distutils.misc_util import is_sequence
is_sequence("ABC") #@
-"""
- )
+""")
inferred = node.inferred()
self.assertIsInstance(inferred[0], nodes.Const)
@@ -215,9 +214,7 @@
assert result[2].lineno == 12
def test_ancestors_patching_class_recursion(self) -> None:
- node = AstroidBuilder(AstroidManager()).string_build(
- textwrap.dedent(
- """
+ node = AstroidBuilder(AstroidManager()).string_build(textwrap.dedent("""
import string
Template = string.Template
@@ -232,9 +229,7 @@
string.Template = A
else:
string.Template = B
- """
- )
- )
+ """))
klass = node["A"]
ancestors = list(klass.ancestors())
self.assertEqual(ancestors[0].qname(), "string.Template")
@@ -243,8 +238,7 @@
# Test for issue https://bitbucket.org/logilab/astroid/issue/84
# This used to crash astroid with a TypeError, because an Uninferable
# node was present in the bases
- node = extract_node(
- """
+ node = extract_node("""
def with_metaclass(meta, *bases):
class metaclass(meta):
def __new__(cls, name, this_bases, d):
@@ -255,21 +249,18 @@
class A(with_metaclass(object, lala.lala)): #@
pass
- """
- )
+ """)
ancestors = list(node.ancestors())
self.assertEqual(len(ancestors), 1)
self.assertEqual(ancestors[0].qname(), "builtins.object")
def test_ancestors_missing_from_function(self) -> None:
# Test for https://www.logilab.org/ticket/122793
- node = extract_node(
- """
+ node = extract_node("""
def gen(): yield
GEN = gen()
next(GEN)
- """
- )
+ """)
self.assertRaises(InferenceError, next, node.infer())
def test_unicode_in_docstring(self) -> None:
@@ -277,8 +268,7 @@
# Test for https://bitbucket.org/logilab/astroid/issues/273/
# In a regular file, "coding: utf-8" would have been used.
- node = extract_node(
- f"""
+ node = extract_node(f"""
from __future__ import unicode_literals
class MyClass(object):
@@ -286,30 +276,26 @@
"With unicode : {'’'} "
instance = MyClass()
- """
- )
+ """)
next(node.value.infer()).as_string()
def test_binop_generates_nodes_with_parents(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
def no_op(*args):
pass
def foo(*args):
def inner(*more_args):
args + more_args #@
return inner
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.Tuple)
self.assertIsNotNone(inferred.parent)
self.assertIsInstance(inferred.parent, nodes.BinOp)
def test_decorator_names_inference_error_leaking(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class Parent(object):
@property
def foo(self):
@@ -319,30 +305,25 @@
@Parent.foo.getter
def foo(self): #@
return super(Child, self).foo + ['oink']
- """
- )
+ """)
inferred = next(node.infer())
self.assertEqual(inferred.decoratornames(), {".Parent.foo.getter"})
def test_recursive_property_method(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
class APropert():
@property
def property(self):
return self
APropert().property
- """
- )
+ """)
next(node.infer())
def test_uninferable_string_argument_of_namedtuple(self) -> None:
- node = extract_node(
- """
+ node = extract_node("""
import collections
collections.namedtuple('{}'.format("a"), '')()
- """
- )
+ """)
next(node.infer())
def test_regression_inference_of_self_in_lambda(self) -> None:
@@ -470,21 +451,17 @@
)
def test_recursion_during_inference(mocked) -> None:
"""Check that we don't crash if we hit the recursion limit during inference."""
- node: nodes.Call = _extract_single_node(
- """
+ node: nodes.Call = _extract_single_node("""
from module import something
something()
- """
- )
+ """)
with pytest.raises(InferenceError) as error:
next(node.infer())
assert error.value.message.startswith("RecursionError raised")
def test_regression_missing_callcontext() -> None:
- node: nodes.Attribute = _extract_single_node(
- textwrap.dedent(
- """
+ node: nodes.Attribute = _extract_single_node(textwrap.dedent("""
import functools
class MockClass:
@@ -494,7 +471,71 @@
enabled = property(functools.partial(_get_option, option='myopt'))
MockClass().enabled
- """
- )
- )
+ """))
assert node.inferred()[0].value == "mystr"
+
+
+def test_regression_root_is_not_a_module() -> None:
+ """Regression test for #2672."""
+ node: nodes.ClassDef = _extract_single_node(textwrap.dedent("""
+ a=eval.__get__(1).__gt__
+
+ @a
+ class c: ...
+ """))
+ assert node.name == "c"
+
+
+def test_regression_eval_get_of_arg() -> None:
+ """Regression test for #2743."""
+ node = _extract_single_node("eval.__get__(1)")
+ with pytest.raises(InferenceError):
+ next(node.infer())
+
+
+def test_regression_no_crash_during_build() -> None:
+ node: nodes.Attribute = extract_node("__()")
+ assert node.args == []
+ assert node.as_string() == "__()"
+
+
+def test_regression_no_crash_on_called_slice() -> None:
+ """Regression test for issue #2721."""
+ node: nodes.Attribute = extract_node(textwrap.dedent("""
+ s = slice(-2)
+ @s()
+ @six.add_metaclass()
+ class a: ...
+ """))
+ assert isinstance(node, nodes.ClassDef)
+ assert node.name == "a"
+
+
+def test_regression_infer_dict_literal_comparison_uninferable() -> None:
+ """Regression test for issue #2522."""
+ node = extract_node("{{}}>0")
+ inferred = next(node.infer())
+ assert inferred.value == Uninferable
+
+
+def test_regression_infer_namedtuple_invalid_fieldname_error() -> None:
+ """Regression test for issue #2519."""
+ code = """
+ from collections import namedtuple
+ namedtuple('a','}')
+ """
+ node = extract_node(code)
+ inferred = next(node.infer())
+ assert inferred.value == Uninferable
+
+
+def test_regression_parse_deeply_nested_parentheses() -> None:
+ """Regression test for issue #2643."""
+ with pytest.raises(AstroidSyntaxError, match="Parsing Python code failed:") as ctx:
+ extract_node(
+ "A=((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c,j=t"
+ )
+ expected = (
+ SyntaxError if platform.python_implementation() == "PyPy" else MemoryError
+ )
+ assert isinstance(ctx.value.error, expected)
diff --git a/tests/test_scoped_nodes.py b/tests/test_scoped_nodes.py
index f3244c6..641a69a 100644
--- a/tests/test_scoped_nodes.py
+++ b/tests/test_scoped_nodes.py
@@ -29,7 +29,7 @@
util,
)
from astroid.bases import BoundMethod, Generator, Instance, UnboundMethod
-from astroid.const import WIN32
+from astroid.const import PY312_PLUS, WIN32
from astroid.exceptions import (
AstroidBuildingError,
AttributeInferenceError,
@@ -134,47 +134,39 @@
self.assertEqual(res, ["Aaa", "func", "name", "other"])
def test_public_names(self) -> None:
- m = builder.parse(
- """
+ m = builder.parse("""
name = 'a'
_bla = 2
other = 'o'
class Aaa: pass
def func(): print('yo')
__all__ = 'Aaa', '_bla', 'name'
- """
- )
+ """)
values = sorted(["Aaa", "name", "other", "func"])
self.assertEqual(sorted(m.public_names()), values)
- m = builder.parse(
- """
+ m = builder.parse("""
name = 'a'
_bla = 2
other = 'o'
class Aaa: pass
def func(): return 'yo'
- """
- )
+ """)
res = sorted(m.public_names())
self.assertEqual(res, values)
- m = builder.parse(
- """
+ m = builder.parse("""
from missing import tzop
trop = "test"
__all__ = (trop, "test1", tzop, 42)
- """
- )
+ """)
res = sorted(m.public_names())
self.assertEqual(res, ["trop", "tzop"])
- m = builder.parse(
- """
+ m = builder.parse("""
test = tzop = 42
__all__ = ('test', ) + ('tzop', )
- """
- )
+ """)
res = sorted(m.public_names())
self.assertEqual(res, ["test", "tzop"])
@@ -292,12 +284,10 @@
@staticmethod
def test_singleline_docstring() -> None:
- data = textwrap.dedent(
- """\
+ data = textwrap.dedent("""\
'''Hello World'''
foo = 1
- """
- )
+ """)
module = builder.parse(data, __name__)
assert isinstance(module.doc_node, nodes.Const)
assert module.doc_node.lineno == 1
@@ -307,15 +297,13 @@
@staticmethod
def test_multiline_docstring() -> None:
- data = textwrap.dedent(
- """\
+ data = textwrap.dedent("""\
'''Hello World
Also on this line.
'''
foo = 1
- """
- )
+ """)
module = builder.parse(data, __name__)
assert isinstance(module.doc_node, nodes.Const)
@@ -326,15 +314,13 @@
@staticmethod
def test_comment_before_docstring() -> None:
- data = textwrap.dedent(
- """\
+ data = textwrap.dedent("""\
# Some comment
'''This is
a multiline docstring.
'''
- """
- )
+ """)
module = builder.parse(data, __name__)
assert isinstance(module.doc_node, nodes.Const)
@@ -345,11 +331,9 @@
@staticmethod
def test_without_docstring() -> None:
- data = textwrap.dedent(
- """\
+ data = textwrap.dedent("""\
foo = 1
- """
- )
+ """)
module = builder.parse(data, __name__)
assert module.doc_node is None
@@ -416,16 +400,10 @@
self.assertEqual(func.args.format_args(), "a, b, c, d")
def test_format_args_keyword_only_args(self) -> None:
- node = (
- builder.parse(
- """
+ node = builder.parse("""
def test(a: int, *, b: dict):
pass
- """
- )
- .body[-1]
- .args
- )
+ """).body[-1].args
formatted = node.format_args()
self.assertEqual(formatted, "a: int, *, b: dict")
@@ -446,8 +424,7 @@
self.assertFalse(func.is_abstract(pass_is_abstract=False))
def test_is_abstract_decorated(self) -> None:
- methods = builder.extract_node(
- """
+ methods = builder.extract_node("""
import abc
class Klass(object):
@@ -463,8 +440,7 @@
@some_other_decorator
def method2(self): #@
pass
- """
- )
+ """)
assert len(methods) == 3
prop, method1, method2 = methods
assert isinstance(prop, nodes.FunctionDef)
@@ -658,8 +634,7 @@
self.assertEqual(func2.implicit_parameters(), 0)
def test_type_builtin_descriptor_subclasses(self) -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
class classonlymethod(classmethod):
pass
class staticonlymethod(staticmethod):
@@ -678,8 +653,7 @@
@staticmethod
def stcmethod(cls):
pass
- """
- )
+ """)
node = astroid.locals["Node"][0]
self.assertEqual(node.locals["clsmethod_subclass"][0].type, "classmethod")
self.assertEqual(node.locals["clsmethod"][0].type, "classmethod")
@@ -687,8 +661,7 @@
self.assertEqual(node.locals["stcmethod"][0].type, "staticmethod")
def test_decorator_builtin_descriptors(self) -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
def static_decorator(platform=None, order=50):
def wrapper(f):
f.cgm_module = True
@@ -749,8 +722,7 @@
@long_classmethod_decorator()
def long_classmethod(cls):
pass
- """
- )
+ """)
node = astroid.locals["SomeClass"][0]
self.assertEqual(node.locals["static"][0].type, "staticmethod")
self.assertEqual(node.locals["classmethod"][0].type, "classmethod")
@@ -761,12 +733,10 @@
self.assertEqual(node.locals["long_classmethod"][0].type, "classmethod")
def test_igetattr(self) -> None:
- func = builder.extract_node(
- """
+ func = builder.extract_node("""
def test():
pass
- """
- )
+ """)
assert isinstance(func, nodes.FunctionDef)
func.instance_attrs["value"] = [nodes.Const(42)]
value = func.getattr("value")
@@ -778,74 +748,62 @@
self.assertEqual(inferred.value, 42)
def test_return_annotation_is_not_the_last(self) -> None:
- func = builder.extract_node(
- """
+ func = builder.extract_node("""
def test() -> bytes:
pass
pass
return
- """
- )
+ """)
last_child = func.last_child()
self.assertIsInstance(last_child, nodes.Return)
self.assertEqual(func.tolineno, 5)
def test_method_init_subclass(self) -> None:
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
class MyClass:
def __init_subclass__(cls):
pass
- """
- )
+ """)
method = klass["__init_subclass__"]
self.assertEqual([n.name for n in method.args.args], ["cls"])
self.assertEqual(method.type, "classmethod")
def test_dunder_class_local_to_method(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class MyClass:
def test(self):
__class__ #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
self.assertEqual(inferred.name, "MyClass")
def test_dunder_class_local_to_function(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def test(self):
__class__ #@
- """
- )
+ """)
with self.assertRaises(NameInferenceError):
next(node.infer())
def test_dunder_class_local_to_classmethod(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class MyClass:
@classmethod
def test(cls):
__class__ #@
- """
- )
+ """)
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
self.assertEqual(inferred.name, "MyClass")
@staticmethod
def test_singleline_docstring() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
def foo():
'''Hello World'''
bar = 1
- """
- )
+ """)
func: nodes.FunctionDef = builder.extract_node(code) # type: ignore[assignment]
assert isinstance(func.doc_node, nodes.Const)
@@ -856,16 +814,14 @@
@staticmethod
def test_multiline_docstring() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
def foo():
'''Hello World
Also on this line.
'''
bar = 1
- """
- )
+ """)
func: nodes.FunctionDef = builder.extract_node(code) # type: ignore[assignment]
assert isinstance(func.doc_node, nodes.Const)
@@ -876,15 +832,13 @@
@staticmethod
def test_multiline_docstring_async() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
async def foo(var: tuple = ()):
'''Hello
World
'''
- """
- )
+ """)
func: nodes.FunctionDef = builder.extract_node(code) # type: ignore[assignment]
assert isinstance(func.doc_node, nodes.Const)
@@ -895,8 +849,7 @@
@staticmethod
def test_docstring_special_cases() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
def f1(var: tuple = ()): #@
'Hello World'
@@ -909,8 +862,7 @@
def f4(): #@
# It should work with comments too
'Hello World'
- """
- )
+ """)
ast_nodes: list[nodes.FunctionDef] = builder.extract_node(code) # type: ignore[assignment]
assert len(ast_nodes) == 4
@@ -940,48 +892,78 @@
@staticmethod
def test_without_docstring() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
def foo():
bar = 1
- """
- )
+ """)
func: nodes.FunctionDef = builder.extract_node(code) # type: ignore[assignment]
assert func.doc_node is None
@staticmethod
def test_display_type() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
def foo():
bar = 1
- """
- )
+ """)
func: nodes.FunctionDef = builder.extract_node(code) # type: ignore[assignment]
assert func.display_type() == "Function"
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
class A:
def foo(self): #@
bar = 1
- """
- )
+ """)
func: nodes.FunctionDef = builder.extract_node(code) # type: ignore[assignment]
assert func.display_type() == "Method"
@staticmethod
def test_inference_error() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
def foo():
bar = 1
- """
- )
+ """)
func: nodes.FunctionDef = builder.extract_node(code) # type: ignore[assignment]
with pytest.raises(AttributeInferenceError):
func.getattr("")
+ @staticmethod
+ def test_blockstart_tolineno() -> None:
+ code = textwrap.dedent("""\
+ def f1(bar: str) -> None: #@
+ pass
+
+ def f2( #@
+ bar: str) -> None:
+ pass
+
+ def f3( #@
+ bar: str
+ ) -> None:
+ pass
+
+ def f4( #@
+ bar: str
+ ):
+ pass
+
+ def f5( #@
+ bar: str):
+ pass
+ """)
+ ast_nodes: list[nodes.FunctionDef] = builder.extract_node(code) # type: ignore[assignment]
+ assert len(ast_nodes) == 5
+
+ assert ast_nodes[0].blockstart_tolineno == 1
+
+ assert ast_nodes[1].blockstart_tolineno == 5
+
+ assert ast_nodes[2].blockstart_tolineno == 10
+
+ # Unimplemented, will return line 14 for now.
+ # assert ast_nodes[3].blockstart_tolineno == 15
+
+ assert ast_nodes[4].blockstart_tolineno == 19
+
class ClassNodeTest(ModuleLoader, unittest.TestCase):
def test_dict_interface(self) -> None:
@@ -1016,27 +998,23 @@
self.assertEqual(len(cls.getattr("__mro__")), 1)
def test__mro__attribute(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A(object): pass
class B(object): pass
class C(A, B): pass
- """
- )
+ """)
assert isinstance(node, nodes.ClassDef)
mro = node.getattr("__mro__")[0]
self.assertIsInstance(mro, nodes.Tuple)
self.assertEqual(mro.elts, node.mro())
def test__bases__attribute(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class A(object): pass
class B(object): pass
class C(A, B): pass
class D(C): pass
- """
- )
+ """)
assert isinstance(node, nodes.ClassDef)
bases = node.getattr("__bases__")[0]
self.assertIsInstance(bases, nodes.Tuple)
@@ -1078,8 +1056,7 @@
self.assertEqual(r_sibling.name, "YOUPI")
def test_local_attr_ancestors(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
class A():
def __init__(self): pass
class B(A): pass
@@ -1087,8 +1064,7 @@
class D(object): pass
class F(): pass
class E(F, D): pass
- """
- )
+ """)
# Test old-style (Python 2) / new-style (Python 3+) ancestors lookups
klass2 = module["C"]
it = klass2.local_attr_ancestors("__init__")
@@ -1112,16 +1088,14 @@
self.assertRaises(StopIteration, partial(next, it))
def test_local_attr_mro(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
class A(object):
def __init__(self): pass
class B(A):
def __init__(self, arg, arg2): pass
class C(A): pass
class D(C, B): pass
- """
- )
+ """)
dclass = module["D"]
init = dclass.local_attr("__init__")[0]
self.assertIsInstance(init, nodes.FunctionDef)
@@ -1353,46 +1327,39 @@
self.assertEqual(astroid["g2"].tolineno, 11)
def test_metaclass_error(self) -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
class Test(object):
__metaclass__ = typ
- """
- )
+ """)
klass = astroid["Test"]
self.assertFalse(klass.metaclass())
def test_metaclass_yes_leak(self) -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
# notice `ab` instead of `abc`
from ab import ABCMeta
class Meta(object):
__metaclass__ = ABCMeta
- """
- )
+ """)
klass = astroid["Meta"]
self.assertIsNone(klass.metaclass())
def test_metaclass_type(self) -> None:
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
def with_metaclass(meta, base=object):
return meta("NewBase", (base, ), {})
class ClassWithMeta(with_metaclass(type)): #@
pass
- """
- )
+ """)
assert isinstance(klass, nodes.ClassDef)
self.assertEqual(
["NewBase", "object"], [base.name for base in klass.ancestors()]
)
def test_no_infinite_metaclass_loop(self) -> None:
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
class SSS(object):
class JJJ(object):
@@ -1407,8 +1374,7 @@
class BBB(AAA.JJJ):
pass
- """
- )
+ """)
assert isinstance(klass, nodes.ClassDef)
self.assertFalse(_is_metaclass(klass))
ancestors = [base.name for base in klass.ancestors()]
@@ -1416,8 +1382,7 @@
self.assertIn("JJJ", ancestors)
def test_no_infinite_metaclass_loop_with_redefine(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
import datetime
class A(datetime.date): #@
@@ -1430,21 +1395,18 @@
datetime.date = A
datetime.date = B
- """
- )
+ """)
for klass in ast_nodes:
self.assertEqual(None, klass.metaclass())
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
def test_metaclass_generator_hack(self):
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
import six
class WithMeta(six.with_metaclass(type, object)): #@
pass
- """
- )
+ """)
assert isinstance(klass, nodes.ClassDef)
self.assertEqual(["object"], [base.name for base in klass.ancestors()])
self.assertEqual("type", klass.metaclass().name)
@@ -1452,26 +1414,22 @@
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
def test_metaclass_generator_hack_enum_base(self):
"""Regression test for https://github.com/pylint-dev/pylint/issues/5935"""
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
import six
from enum import Enum, EnumMeta
class PetEnumPy2Metaclass(six.with_metaclass(EnumMeta, Enum)): #@
DOG = "dog"
- """
- )
+ """)
self.assertEqual(list(klass.local_attr_ancestors("DOG")), [])
def test_add_metaclass(self) -> None:
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
import abc
class WithMeta(object, metaclass=abc.ABCMeta):
pass
- """
- )
+ """)
assert isinstance(klass, nodes.ClassDef)
inferred = next(klass.infer())
metaclass = inferred.metaclass()
@@ -1480,34 +1438,29 @@
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
def test_using_invalid_six_add_metaclass_call(self):
- klass = builder.extract_node(
- """
+ klass = builder.extract_node("""
import six
@six.add_metaclass()
class Invalid(object):
pass
- """
- )
+ """)
inferred = next(klass.infer())
self.assertIsNone(inferred.metaclass())
@staticmethod
def test_with_invalid_metaclass():
- klass = extract_node(
- """
+ klass = extract_node("""
class InvalidAsMetaclass: ...
class Invalid(metaclass=InvalidAsMetaclass()): #@
pass
- """
- )
+ """)
inferred = next(klass.infer())
metaclass = inferred.metaclass()
assert isinstance(metaclass, Instance)
def test_nonregr_infer_callresult(self) -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
class Delegate(object):
def __get__(self, obj, cls):
return getattr(obj._subject, self.attribute)
@@ -1517,16 +1470,14 @@
builder = CompositeBuilder(result, composite)
tgts = builder()
- """
- )
+ """)
instance = astroid["tgts"]
# used to raise "'_Yes' object is not iterable", see
# https://bitbucket.org/logilab/astroid/issue/17
self.assertEqual(list(instance.infer()), [util.Uninferable])
def test_slots(self) -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
from collections import deque
from textwrap import dedent
@@ -1550,8 +1501,7 @@
pass
class Ten(object): #@
__slots__ = dict({"a": "b", "c": "d"})
- """
- )
+ """)
expected = [
("First", ("a", "b")),
("Second", ("a",)),
@@ -1572,13 +1522,11 @@
self.assertEqual(list(expected_value), [node.value for node in slots])
def test_slots_for_dict_keys(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
class Issue(object):
SlotDefaults = {'id': 0, 'id1':1}
__slots__ = SlotDefaults.keys()
- """
- )
+ """)
cls = module["Issue"]
slots = cls.slots()
self.assertEqual(len(slots), 2)
@@ -1586,26 +1534,22 @@
self.assertEqual(slots[1].value, "id1")
def test_slots_empty_list_of_slots(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
class Klass(object):
__slots__ = ()
- """
- )
+ """)
cls = module["Klass"]
self.assertEqual(cls.slots(), [])
def test_slots_taken_from_parents(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
class FirstParent(object):
__slots__ = ('a', 'b', 'c')
class SecondParent(FirstParent):
__slots__ = ('d', 'e')
class Third(SecondParent):
__slots__ = ('d', )
- """
- )
+ """)
cls = module["Third"]
slots = cls.slots()
self.assertEqual(
@@ -1613,15 +1557,13 @@
)
def test_all_ancestors_need_slots(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
class A(object):
__slots__ = ('a', )
class B(A): pass
class C(B):
__slots__ = ('a', )
- """
- )
+ """)
cls = module["C"]
self.assertIsNone(cls.slots())
cls = module["B"]
@@ -1652,8 +1594,7 @@
@unittest.skipUnless(HAS_SIX, "These tests require the six library")
def test_with_metaclass_mro(self):
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
import six
class C(object):
@@ -1662,13 +1603,11 @@
pass
class A(six.with_metaclass(type, B)):
pass
- """
- )
+ """)
self.assertEqualMro(astroid["A"], ["A", "B", "C", "object"])
def test_mro(self) -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
class C(object): pass
class D(dict, C): pass
@@ -1703,8 +1642,7 @@
pass
class Duplicates(str, str): pass
- """
- )
+ """)
self.assertEqualMro(astroid["D"], ["D", "dict", "C", "object"])
self.assertEqualMro(astroid["D1"], ["D1", "B1", "C1", "A1", "object"])
self.assertEqualMro(astroid["E1"], ["E1", "C1", "B1", "A1", "object"])
@@ -1758,9 +1696,31 @@
self.assertIsInstance(cm.exception, MroError)
self.assertIsInstance(cm.exception, ResolveError)
+ def test_mro_circular_name_rebinding(self) -> None:
+ """MRO computation should handle circular name rebinding.
+
+ When a module-level name is rebound to a subclass of itself,
+ _infer_last follows the rebinding and returns the subclass.
+ The MRO computation should resolve the cycle by falling back
+ to the original class.
+
+ Regression test for https://github.com/pylint-dev/astroid/issues/2967
+ """
+ astroid = builder.parse("""
+ import pdb
+
+ class CustomPdb(pdb.Pdb):
+ pass
+
+ pdb.Pdb = CustomPdb
+ """)
+ self.assertEqualMro(
+ astroid["CustomPdb"],
+ ["CustomPdb", "Pdb", "Bdb", "Cmd", "object"],
+ )
+
def test_mro_with_factories(self) -> None:
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
def MixinFactory(cls):
mixin_name = '{}Mixin'.format(cls.__name__)
mixin_bases = (object,)
@@ -1780,8 +1740,7 @@
class FinalClass(ClassB):
def __init__(self):
self.name = 'x'
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
self.assertEqualMro(
cls,
@@ -1799,8 +1758,7 @@
)
def test_mro_with_attribute_classes(self) -> None:
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
class A:
pass
class B:
@@ -1812,107 +1770,93 @@
scope.B = B
class C(scope.A, scope.B):
pass
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
self.assertEqualMro(cls, ["C", "A", "B", "object"])
def test_mro_generic_1(self):
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
import typing
T = typing.TypeVar('T')
class A(typing.Generic[T]): ...
class B: ...
class C(A[T], B): ...
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
self.assertEqualMroQName(
cls, [".C", ".A", "typing.Generic", ".B", "builtins.object"]
)
def test_mro_generic_2(self):
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
from typing import Generic, TypeVar
T = TypeVar('T')
class A: ...
class B(Generic[T]): ...
class C(Generic[T], A, B[T]): ...
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
self.assertEqualMroQName(
cls, [".C", ".A", ".B", "typing.Generic", "builtins.object"]
)
def test_mro_generic_3(self):
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
from typing import Generic, TypeVar
T = TypeVar('T')
class A: ...
class B(A, Generic[T]): ...
class C(Generic[T]): ...
class D(B[T], C[T], Generic[T]): ...
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
self.assertEqualMroQName(
cls, [".D", ".B", ".A", ".C", "typing.Generic", "builtins.object"]
)
def test_mro_generic_4(self):
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
from typing import Generic, TypeVar
T = TypeVar('T')
class A: ...
class B(Generic[T]): ...
class C(A, Generic[T], B[T]): ...
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
self.assertEqualMroQName(
cls, [".C", ".A", ".B", "typing.Generic", "builtins.object"]
)
def test_mro_generic_5(self):
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
from typing import Generic, TypeVar
T1 = TypeVar('T1')
T2 = TypeVar('T2')
class A(Generic[T1]): ...
class B(Generic[T2]): ...
class C(A[T1], B[T2]): ...
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
self.assertEqualMroQName(
cls, [".C", ".A", ".B", "typing.Generic", "builtins.object"]
)
def test_mro_generic_6(self):
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
from typing import Generic as TGeneric, TypeVar
T = TypeVar('T')
class Generic: ...
class A(Generic): ...
class B(TGeneric[T]): ...
class C(A, B[T]): ...
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
self.assertEqualMroQName(
cls, [".C", ".A", ".Generic", ".B", "typing.Generic", "builtins.object"]
)
def test_mro_generic_7(self):
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
from typing import Generic, TypeVar
T = TypeVar('T')
class A(): ...
@@ -1920,35 +1864,54 @@
class C(A, B[T]): ...
class D: ...
class E(C[str], D): ...
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
self.assertEqualMroQName(
cls, [".E", ".C", ".A", ".B", "typing.Generic", ".D", "builtins.object"]
)
+ @pytest.mark.skipif(not PY312_PLUS, reason="PEP 695 syntax requires Python 3.12")
+ def test_mro_generic_8(self):
+ cls = builder.extract_node("""
+ class A: ...
+ class B[T]: ...
+ class C[T](A, B[T]): ...
+ """)
+ assert isinstance(cls, nodes.ClassDef)
+ self.assertEqualMroQName(cls, [".C", ".A", ".B", "builtins.object"])
+
+ @pytest.mark.skipif(not PY312_PLUS, reason="PEP 695 syntax requires Python 3.12")
+ def test_mro_generic_9(self):
+ cls = builder.extract_node("""
+ from dataclasses import dataclass
+ @dataclass
+ class A: ...
+ @dataclass
+ class B[T]: ...
+ @dataclass
+ class C[T](A, B[T]): ...
+ """)
+ assert isinstance(cls, nodes.ClassDef)
+ self.assertEqualMroQName(cls, [".C", ".A", ".B", "builtins.object"])
+
def test_mro_generic_error_1(self):
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
from typing import Generic, TypeVar
T1 = TypeVar('T1')
T2 = TypeVar('T2')
class A(Generic[T1], Generic[T2]): ...
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
with self.assertRaises(DuplicateBasesError):
cls.mro()
def test_mro_generic_error_2(self):
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
from typing import Generic, TypeVar
T = TypeVar('T')
class A(Generic[T]): ...
class B(A[T], A[T]): ...
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
with self.assertRaises(DuplicateBasesError):
cls.mro()
@@ -1959,8 +1922,7 @@
Regression reported in:
https://github.com/pylint-dev/astroid/issues/1124
"""
- module = parse(
- """
+ module = parse("""
import abc
import typing
import dataclasses
@@ -1972,8 +1934,7 @@
class EarlyBase(typing.Generic[T], MyProtocol): pass
class Base(EarlyBase[T], abc.ABC): pass
class Final(Base[object]): pass
- """
- )
+ """)
class_names = [
"ABC",
"Base",
@@ -1988,26 +1949,22 @@
self.assertEqual(class_names, sorted(i.name for i in final_def.mro()))
def test_generator_from_infer_call_result_parent(self) -> None:
- func = builder.extract_node(
- """
+ func = builder.extract_node("""
import contextlib
@contextlib.contextmanager
def test(): #@
yield
- """
- )
+ """)
assert isinstance(func, nodes.FunctionDef)
result = next(func.infer_call_result(None))
self.assertIsInstance(result, Generator)
self.assertEqual(result.parent, func)
def test_type_three_arguments(self) -> None:
- classes = builder.extract_node(
- """
+ classes = builder.extract_node("""
type('A', (object, ), {"a": 1, "b": 2, missing: 3}) #@
- """
- )
+ """)
assert isinstance(classes, nodes.Call)
first = next(classes.infer())
self.assertIsInstance(first, nodes.ClassDef)
@@ -2021,23 +1978,19 @@
first.getattr("missing")
def test_implicit_metaclass(self) -> None:
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
class A(object):
pass
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
type_cls = nodes.builtin_lookup("type")[1][0]
self.assertEqual(cls.implicit_metaclass(), type_cls)
def test_implicit_metaclass_lookup(self) -> None:
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
class A(object):
pass
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
instance = cls.instantiate_class()
func = cls.getattr("mro")
@@ -2046,29 +1999,24 @@
def test_metaclass_lookup_using_same_class(self) -> None:
"""Check that we don't have recursive attribute access for metaclass."""
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
class A(object): pass
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
self.assertEqual(len(cls.getattr("mro")), 1)
def test_metaclass_lookup_inference_errors(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
class Metaclass(type):
foo = lala
class B(object, metaclass=Metaclass): pass
- """
- )
+ """)
cls = module["B"]
self.assertEqual(util.Uninferable, next(cls.igetattr("foo")))
def test_metaclass_lookup(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
class Metaclass(type):
foo = 42
@classmethod
@@ -2085,8 +2033,7 @@
class A(object, metaclass=Metaclass):
pass
- """
- )
+ """)
acls = module["A"]
normal_attr = next(acls.igetattr("foo"))
self.assertIsInstance(normal_attr, nodes.Const)
@@ -2117,16 +2064,14 @@
self.assertIsInstance(static, nodes.FunctionDef)
def test_local_attr_invalid_mro(self) -> None:
- cls = builder.extract_node(
- """
+ cls = builder.extract_node("""
# A has an invalid MRO, local_attr should fallback
# to using .ancestors.
class A(object, object):
test = 42
class B(A): #@
pass
- """
- )
+ """)
assert isinstance(cls, nodes.ClassDef)
local = cls.local_attr("test")[0]
inferred = next(local.infer())
@@ -2134,8 +2079,7 @@
self.assertEqual(inferred.value, 42)
def test_has_dynamic_getattr(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
class Getattr(object):
def __getattr__(self, attrname):
pass
@@ -2146,8 +2090,7 @@
class ParentGetattr(Getattr):
pass
- """
- )
+ """)
self.assertTrue(module["Getattr"].has_dynamic_getattr())
self.assertTrue(module["Getattribute"].has_dynamic_getattr())
self.assertTrue(module["ParentGetattr"].has_dynamic_getattr())
@@ -2159,30 +2102,26 @@
self.assertFalse(module["SequenceMatcher"].has_dynamic_getattr())
def test_duplicate_bases_namedtuple(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
import collections
_A = collections.namedtuple('A', 'a')
class A(_A): pass
class B(A): pass
- """
- )
+ """)
names = ["B", "A", "A", "tuple", "object"]
mro = module["B"].mro()
class_names = [i.name for i in mro]
self.assertEqual(names, class_names)
def test_instance_bound_method_lambdas(self) -> None:
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
class Test(object): #@
lam = lambda self: self
not_method = lambda xargs: xargs
Test() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
cls = next(ast_nodes[0].infer())
self.assertIsInstance(next(cls.igetattr("lam")), nodes.Lambda)
@@ -2199,8 +2138,7 @@
Test the fact that a method which is a lambda built from
a factory is well inferred as a bound method (bug pylint 2594).
"""
- ast_nodes = builder.extract_node(
- """
+ ast_nodes = builder.extract_node("""
def lambda_factory():
return lambda self: print("Hello world")
@@ -2208,8 +2146,7 @@
f2 = lambda_factory()
MyClass() #@
- """
- )
+ """)
assert isinstance(ast_nodes, list)
cls = next(ast_nodes[0].infer())
self.assertIsInstance(next(cls.igetattr("f2")), nodes.Lambda)
@@ -2219,55 +2156,46 @@
self.assertIsInstance(f2, BoundMethod)
def test_class_extra_decorators_frame_is_not_class(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
def ala():
def bala(): #@
func = 42
- """
- )
+ """)
assert isinstance(ast_node, nodes.FunctionDef)
self.assertEqual(ast_node.extra_decorators, [])
def test_class_extra_decorators_only_callfunc_are_considered(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
class Ala(object):
def func(self): #@
pass
func = 42
- """
- )
+ """)
self.assertEqual(ast_node.extra_decorators, [])
def test_class_extra_decorators_only_assignment_names_are_considered(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
class Ala(object):
def func(self): #@
pass
def __init__(self):
self.func = staticmethod(func)
- """
- )
+ """)
self.assertEqual(ast_node.extra_decorators, [])
def test_class_extra_decorators_only_same_name_considered(self) -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
class Ala(object):
def func(self): #@
pass
bala = staticmethod(func)
- """
- )
+ """)
self.assertEqual(ast_node.extra_decorators, [])
self.assertEqual(ast_node.type, "method")
def test_class_extra_decorators(self) -> None:
- static_method, clsmethod = builder.extract_node(
- """
+ static_method, clsmethod = builder.extract_node("""
class Ala(object):
def static(self): #@
pass
@@ -2275,16 +2203,14 @@
pass
class_method = classmethod(class_method)
static = staticmethod(static)
- """
- )
+ """)
self.assertEqual(len(clsmethod.extra_decorators), 1)
self.assertEqual(clsmethod.type, "classmethod")
self.assertEqual(len(static_method.extra_decorators), 1)
self.assertEqual(static_method.type, "staticmethod")
def test_extra_decorators_only_class_level_assignments(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
def _bind(arg):
return arg.bind
@@ -2298,8 +2224,7 @@
bind = _bind(self)
return bind
A() #@
- """
- )
+ """)
inferred = next(node.infer())
bind = next(inferred.igetattr("bind"))
self.assertIsInstance(bind, nodes.Const)
@@ -2343,13 +2268,11 @@
@staticmethod
def test_singleline_docstring() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
class Foo:
'''Hello World'''
bar = 1
- """
- )
+ """)
node: nodes.ClassDef = builder.extract_node(code) # type: ignore[assignment]
assert isinstance(node.doc_node, nodes.Const)
assert node.doc_node.lineno == 2
@@ -2359,16 +2282,14 @@
@staticmethod
def test_multiline_docstring() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
class Foo:
'''Hello World
Also on this line.
'''
bar = 1
- """
- )
+ """)
node: nodes.ClassDef = builder.extract_node(code) # type: ignore[assignment]
assert isinstance(node.doc_node, nodes.Const)
assert node.doc_node.lineno == 2
@@ -2378,19 +2299,16 @@
@staticmethod
def test_without_docstring() -> None:
- code = textwrap.dedent(
- """\
+ code = textwrap.dedent("""\
class Foo:
bar = 1
- """
- )
+ """)
node: nodes.ClassDef = builder.extract_node(code) # type: ignore[assignment]
assert node.doc_node is None
def test_issue940_metaclass_subclass_property() -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class BaseMeta(type):
@property
def __members__(cls):
@@ -2400,16 +2318,14 @@
class Derived(Parent):
pass
Derived.__members__
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.List)
assert [c.value for c in inferred.elts] == ["a", "property"]
def test_issue940_property_grandchild() -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class Grandparent:
@property
def __members__(self):
@@ -2419,16 +2335,14 @@
class Child(Parent):
pass
Child().__members__
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.List)
assert [c.value for c in inferred.elts] == ["a", "property"]
def test_issue940_metaclass_property() -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class BaseMeta(type):
@property
def __members__(cls):
@@ -2436,16 +2350,14 @@
class Parent(metaclass=BaseMeta):
pass
Parent.__members__
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.List)
assert [c.value for c in inferred.elts] == ["a", "property"]
def test_issue940_with_metaclass_class_context_property() -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class BaseMeta(type):
pass
class Parent(metaclass=BaseMeta):
@@ -2455,32 +2367,28 @@
class Derived(Parent):
pass
Derived.__members__
- """
- )
+ """)
inferred = next(node.infer())
assert not isinstance(inferred, nodes.List)
assert isinstance(inferred, objects.Property)
def test_issue940_metaclass_values_funcdef() -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class BaseMeta(type):
def __members__(cls):
return ['a', 'func']
class Parent(metaclass=BaseMeta):
pass
Parent.__members__()
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, nodes.List)
assert [c.value for c in inferred.elts] == ["a", "func"]
def test_issue940_metaclass_derived_funcdef() -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class BaseMeta(type):
def __members__(cls):
return ['a', 'func']
@@ -2489,16 +2397,14 @@
class Derived(Parent):
pass
Derived.__members__()
- """
- )
+ """)
inferred_result = next(node.infer())
assert isinstance(inferred_result, nodes.List)
assert [c.value for c in inferred_result.elts] == ["a", "func"]
def test_issue940_metaclass_funcdef_is_not_datadescriptor() -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
class BaseMeta(type):
def __members__(cls):
return ['a', 'property']
@@ -2509,8 +2415,7 @@
class Derived(Parent):
pass
Derived.__members__
- """
- )
+ """)
# Here the function is defined on the metaclass, but the property
# is defined on the base class. When loading the attribute in a
# class context, this should return the property object instead of
@@ -2521,8 +2426,7 @@
def test_property_in_body_of_try() -> None:
"""Regression test for https://github.com/pylint-dev/pylint/issues/6596."""
- node: nodes.Return = builder._extract_single_node(
- """
+ node: nodes.Return = builder._extract_single_node("""
def myfunc():
try:
@@ -2538,14 +2442,12 @@
pass
return myfunc() #@
- """
- )
+ """)
next(node.value.infer())
def test_property_in_body_of_if() -> None:
- node: nodes.Return = builder._extract_single_node(
- """
+ node: nodes.Return = builder._extract_single_node("""
def myfunc():
if True:
@@ -2558,21 +2460,18 @@
pass
return myfunc() #@
- """
- )
+ """)
next(node.value.infer())
def test_issue940_enums_as_a_real_world_usecase() -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from enum import Enum
class Sounds(Enum):
bee = "buzz"
cat = "meow"
Sounds.__members__
- """
- )
+ """)
inferred_result = next(node.infer())
assert isinstance(inferred_result, nodes.Dict)
actual = [k.value for k, _ in inferred_result.items]
@@ -2585,15 +2484,13 @@
- `member.value.value` is of type `str`
is inferred as: `repr(member.value.value)`
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from enum import Enum
class Veg(Enum):
TOMATO: str = "sweet"
Veg.TOMATO.value
- """
- )
+ """)
inferred_member_value = node.inferred()[0]
assert isinstance(inferred_member_value, nodes.Const)
assert inferred_member_value.value == "sweet"
@@ -2605,30 +2502,26 @@
- `member.value.value` is `None`
is not inferred
"""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from enum import Enum
class Veg(Enum):
TOMATO: {annotation}
Veg.TOMATO.value
- """
- )
+ """)
inferred_member_value = node.inferred()[0]
assert inferred_member_value.value is None
def test_enums_value2member_map_() -> None:
"""Check the `_value2member_map_` member is present in an Enum class."""
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from enum import Enum
class Veg(Enum):
TOMATO: 1
Veg
- """
- )
+ """)
inferred_class = node.inferred()[0]
assert "_value2member_map_" in inferred_class.locals
@@ -2641,15 +2534,13 @@
is inferred as: `member.value.value`
"""
- node = builder.extract_node(
- f"""
+ node = builder.extract_node(f"""
from enum import Enum
class Veg(Enum):
TOMATO: {annotation} = {value}
Veg.TOMATO.value
- """
- )
+ """)
inferred_member_value = node.inferred()[0]
assert isinstance(inferred_member_value, nodes.Const)
assert inferred_member_value.value == value
@@ -2669,16 +2560,14 @@
is inferred as: `member.value.as_string()`.
"""
- member = builder.extract_node(
- f"""
+ member = builder.extract_node(f"""
from enum import Enum
class Veg(Enum):
TOMATO: {annotation} = {value}
Veg.TOMATO.value
- """
- )
+ """)
inferred_member_value = member.inferred()[0]
assert not isinstance(inferred_member_value, nodes.Const)
@@ -2686,16 +2575,14 @@
def test_metaclass_cannot_infer_call_yields_an_instance() -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
from undefined import Undefined
class Meta(type):
__call__ = Undefined
class A(metaclass=Meta):
pass
A()
- """
- )
+ """)
inferred = next(node.infer())
assert isinstance(inferred, Instance)
@@ -2703,48 +2590,34 @@
@pytest.mark.parametrize(
"func",
[
- textwrap.dedent(
- """
+ textwrap.dedent("""
def func(a, b, /, d, e):
pass
- """
- ),
- textwrap.dedent(
- """
+ """),
+ textwrap.dedent("""
def func(a, b=None, /, d=None, e=None):
pass
- """
- ),
- textwrap.dedent(
- """
+ """),
+ textwrap.dedent("""
def func(a, other, other, b=None, /, d=None, e=None):
pass
- """
- ),
- textwrap.dedent(
- """
+ """),
+ textwrap.dedent("""
def func(a, other, other, b=None, /, d=None, e=None, **kwargs):
pass
- """
- ),
- textwrap.dedent(
- """
+ """),
+ textwrap.dedent("""
def name(p1, p2, /, p_or_kw, *, kw):
pass
- """
- ),
- textwrap.dedent(
- """
+ """),
+ textwrap.dedent("""
def __init__(self, other=(), /, **kw):
pass
- """
- ),
- textwrap.dedent(
- """
+ """),
+ textwrap.dedent("""
def __init__(self: int, other: float, /, **kw):
pass
- """
- ),
+ """),
],
)
def test_posonlyargs_python_38(func):
@@ -2753,11 +2626,9 @@
def test_posonlyargs_default_value() -> None:
- ast_node = builder.extract_node(
- """
+ ast_node = builder.extract_node("""
def func(a, b=1, /, c=2): pass
- """
- )
+ """)
last_param = ast_node.args.default_value("c")
assert isinstance(last_param, nodes.Const)
assert last_param.value == 2
@@ -2769,8 +2640,7 @@
def test_ancestor_with_generic() -> None:
# https://github.com/pylint-dev/astroid/issues/942
- tree = builder.parse(
- """
+ tree = builder.parse("""
from typing import TypeVar, Generic
T = TypeVar("T")
class A(Generic[T]):
@@ -2778,8 +2648,7 @@
print("hello")
class B(A[T]): pass
class C(B[str]): pass
- """
- )
+ """)
inferred_b = next(tree["B"].infer())
assert [cdef.name for cdef in inferred_b.ancestors()] == ["A", "Generic", "object"]
@@ -2793,22 +2662,42 @@
def test_slots_duplicate_bases_issue_1089() -> None:
- astroid = builder.parse(
- """
+ astroid = builder.parse("""
class First(object, object): #@
pass
- """
- )
+ """)
with pytest.raises(NotImplementedError):
astroid["First"].slots()
+def test_import_with_global() -> None:
+ code = builder.parse("""
+ def f1():
+ global platform
+ from sys import platform as plat
+ platform = plat
+
+ def f2():
+ global os, RE, deque, VERSION, Path
+ import os
+ import re as RE
+ from collections import deque
+ from sys import version as VERSION
+ from pathlib import *
+ """)
+ assert "platform" in code.locals
+ assert "os" in code.locals
+ assert "RE" in code.locals
+ assert "deque" in code.locals
+ assert "VERSION" in code.locals
+ assert "Path" in code.locals
+
+
class TestFrameNodes:
@staticmethod
def test_frame_node():
"""Test if the frame of FunctionDef, ClassDef and Module is correctly set."""
- module = builder.parse(
- """
+ module = builder.parse("""
def func():
var_1 = x
return var_1
@@ -2821,8 +2710,7 @@
pass
VAR = lambda y = (named_expr := "walrus"): print(y)
- """
- )
+ """)
function = module.body[0]
assert function.frame() == function
assert function.frame() == function
@@ -2845,15 +2733,44 @@
assert module.frame() == module
@staticmethod
+ def test_frame_node_for_decorators():
+ code = builder.extract_node("""
+ def deco(var):
+ def inner(arg):
+ ...
+ return inner
+
+ @deco(
+ x := 1 #@
+ )
+ def func(): #@
+ ...
+
+ @deco(
+ y := 2 #@
+ )
+ class A: #@
+ ...
+ """)
+ name_expr_node1, func_node, name_expr_node2, class_node = code
+ module = func_node.root()
+ assert name_expr_node1.scope() == module
+ assert name_expr_node1.frame() == module
+ assert name_expr_node2.scope() == module
+ assert name_expr_node2.frame() == module
+ assert module.locals.get("x") == [name_expr_node1.target]
+ assert module.locals.get("y") == [name_expr_node2.target]
+ assert "x" not in func_node.locals
+ assert "y" not in class_node.locals
+
+ @staticmethod
def test_non_frame_node():
"""Test if the frame of non frame nodes is set correctly."""
- module = builder.parse(
- """
+ module = builder.parse("""
VAR_ONE = 1
VAR_TWO = [x for x in range(1)]
- """
- )
+ """)
assert module.body[0].frame() == module
assert module.body[0].frame() == module
diff --git a/tests/test_stdlib.py b/tests/test_stdlib.py
index 4027faa..d97fc86 100644
--- a/tests/test_stdlib.py
+++ b/tests/test_stdlib.py
@@ -13,12 +13,10 @@
def test_sys_builtin_module_names(self) -> None:
"""Test that we can gather the elements of a living tuple object."""
- node = _extract_single_node(
- """
+ node = _extract_single_node("""
import sys
sys.builtin_module_names
- """
- )
+ """)
inferred = list(node.infer())
assert len(inferred) == 1
assert isinstance(inferred[0], nodes.Tuple)
@@ -26,12 +24,10 @@
def test_sys_modules(self) -> None:
"""Test that we can gather the items of a living dict object."""
- node = _extract_single_node(
- """
+ node = _extract_single_node("""
import sys
sys.modules
- """
- )
+ """)
inferred = list(node.infer())
assert len(inferred) == 1
assert isinstance(inferred[0], nodes.Dict)
diff --git a/tests/test_transforms.py b/tests/test_transforms.py
index 87b26a5..d58c5a7 100644
--- a/tests/test_transforms.py
+++ b/tests/test_transforms.py
@@ -16,8 +16,6 @@
from astroid.brain.brain_dataclasses import _looks_like_dataclass_field_call
from astroid.const import IS_PYPY
from astroid.manager import AstroidManager
-from astroid.nodes.node_classes import Call, Compare, Const, Name
-from astroid.nodes.scoped_nodes import FunctionDef, Module
from tests.testdata.python3.recursion_error import LONG_CHAINED_METHOD_CALL
@@ -39,24 +37,22 @@
def setUp(self) -> None:
self.transformer = transforms.TransformVisitor()
- def parse_transform(self, code: str) -> Module:
+ def parse_transform(self, code: str) -> nodes.Module:
module = parse(code, apply_transforms=False)
return self.transformer.visit(module)
def test_function_inlining_transform(self) -> None:
- def transform_call(node: Call) -> Const:
+ def transform_call(node: nodes.Call) -> nodes.Const:
# Let's do some function inlining
inferred = next(node.infer())
return inferred
self.transformer.register_transform(nodes.Call, transform_call)
- module = self.parse_transform(
- """
+ module = self.parse_transform("""
def test(): return 42
test() #@
- """
- )
+ """)
self.assertIsInstance(module.body[1], nodes.Expr)
self.assertIsInstance(module.body[1].value, nodes.Const)
@@ -65,34 +61,32 @@
def test_recursive_transforms_into_astroid_fields(self) -> None:
# Test that the transformer walks properly the tree
# by going recursively into the _astroid_fields per each node.
- def transform_compare(node: Compare) -> Const:
+ def transform_compare(node: nodes.Compare) -> nodes.Const:
# Let's check the values of the ops
_, right = node.ops[0]
# Assume they are Consts and they were transformed before
# us.
return nodes.const_factory(node.left.value < right.value)
- def transform_name(node: Name) -> Const:
+ def transform_name(node: nodes.Name) -> nodes.Const:
# Should be Consts
return next(node.infer())
self.transformer.register_transform(nodes.Compare, transform_compare)
self.transformer.register_transform(nodes.Name, transform_name)
- module = self.parse_transform(
- """
+ module = self.parse_transform("""
a = 42
b = 24
a < b
- """
- )
+ """)
self.assertIsInstance(module.body[2], nodes.Expr)
self.assertIsInstance(module.body[2].value, nodes.Const)
self.assertFalse(module.body[2].value.value)
def test_transform_patches_locals(self) -> None:
- def transform_function(node: FunctionDef) -> None:
+ def transform_function(node: nodes.FunctionDef) -> None:
assign = nodes.Assign(
parent=node,
lineno=node.lineno,
@@ -114,12 +108,10 @@
self.transformer.register_transform(nodes.FunctionDef, transform_function)
- module = self.parse_transform(
- """
+ module = self.parse_transform("""
def test():
pass
- """
- )
+ """)
func = module.body[0]
self.assertEqual(len(func.body), 2)
@@ -127,17 +119,16 @@
self.assertEqual(func.body[1].as_string(), "value = 42")
def test_predicates(self) -> None:
- def transform_call(node: Call) -> Const:
+ def transform_call(node: nodes.Call) -> nodes.Const:
inferred = next(node.infer())
return inferred
- def should_inline(node: Call) -> bool:
+ def should_inline(node: nodes.Call) -> bool:
return node.func.name.startswith("inlineme")
self.transformer.register_transform(nodes.Call, transform_call, should_inline)
- module = self.parse_transform(
- """
+ module = self.parse_transform("""
def inlineme_1():
return 24
def dont_inline_me():
@@ -147,8 +138,7 @@
inlineme_1()
dont_inline_me()
inlineme_2()
- """
- )
+ """)
values = module.body[-3:]
self.assertIsInstance(values[0], nodes.Expr)
self.assertIsInstance(values[0].value, nodes.Const)
@@ -165,7 +155,7 @@
# on a partially constructed tree anymore, which was the
# source of crashes in the past when certain inference rules
# were used in a transform.
- def transform_function(node: FunctionDef) -> Const:
+ def transform_function(node: nodes.FunctionDef) -> nodes.Const:
if node.decorators:
for decorator in node.decorators.nodes:
inferred = next(decorator.infer())
@@ -175,8 +165,7 @@
manager = MANAGER
with add_transform(manager, nodes.FunctionDef, transform_function):
- module = builder.parse(
- """
+ module = builder.parse("""
import abc
from abc import abstractmethod
@@ -188,8 +177,7 @@
@abstractmethod
def bala(self):
return 42
- """
- )
+ """)
cls = module["A"]
ala = cls.body[0]
@@ -201,7 +189,7 @@
def test_transforms_are_called_for_builtin_modules(self) -> None:
# Test that transforms are called for builtin modules.
- def transform_function(node: FunctionDef) -> FunctionDef:
+ def transform_function(node: nodes.FunctionDef) -> nodes.FunctionDef:
name = nodes.AssignName(
name="value",
lineno=0,
@@ -215,7 +203,7 @@
manager = MANAGER
- def predicate(node: FunctionDef) -> bool:
+ def predicate(node: nodes.FunctionDef) -> bool:
return node.root().name == "time"
with add_transform(manager, nodes.FunctionDef, transform_function, predicate):
@@ -252,8 +240,7 @@
self.transformer.register_transform(nodes.ClassDef, transform_class)
- self.parse_transform(
- """
+ self.parse_transform("""
# Change environ to automatically call putenv() if it exists
import os
putenv = os.putenv
@@ -264,14 +251,13 @@
pass
else:
import UserDict
- """
- )
+ """)
@pytest.mark.skipif(
IS_PYPY, reason="Could not find a useful recursion limit on all versions"
)
def test_transform_aborted_if_recursion_limited(self):
- def transform_call(node: Call) -> Const:
+ def transform_call(node: nodes.Call) -> nodes.Const:
return node
self.transformer.register_transform(
diff --git a/tests/test_type_params.py b/tests/test_type_params.py
index 6398f78..021aa9a 100644
--- a/tests/test_type_params.py
+++ b/tests/test_type_params.py
@@ -5,11 +5,14 @@
import pytest
from astroid import extract_node
-from astroid.const import PY312_PLUS
+from astroid.const import PY312_PLUS, PY313_PLUS
from astroid.nodes import (
AssignName,
+ List,
+ Name,
ParamSpec,
Subscript,
+ Tuple,
TypeAlias,
TypeVar,
TypeVarTuple,
@@ -26,6 +29,7 @@
assert isinstance(node.type_params[0].name, AssignName)
assert node.type_params[0].name.name == "T"
assert node.type_params[0].bound is None
+ assert node.type_params[0].default_value is None
assert isinstance(node.value, Subscript)
assert node.value.value.name == "list"
@@ -41,12 +45,46 @@
assert assigned is node.value
+def test_type_var() -> None:
+ node = extract_node("type Point[T: int] = T")
+ param = node.type_params[0]
+ assert isinstance(param, TypeVar)
+ assert isinstance(param.bound, Name)
+ assert param.bound.name == "int"
+ assert param.default_value is None
+
+
+@pytest.mark.skipif(not PY313_PLUS, reason="Type parameter defaults were added in 313")
+def test_type_var_defaults() -> None:
+ node = extract_node("type Point[T: int = int] = T")
+ param = node.type_params[0]
+ assert isinstance(param, TypeVar)
+ assert isinstance(param.bound, Name)
+ assert param.bound.name == "int"
+ assert isinstance(param.default_value, Name)
+ assert param.default_value.name == "int"
+
+
def test_type_param_spec() -> None:
node = extract_node("type Alias[**P] = Callable[P, int]")
params = node.type_params[0]
assert isinstance(params, ParamSpec)
assert isinstance(params.name, AssignName)
assert params.name.name == "P"
+ assert params.default_value is None
+
+ assert node.inferred()[0] is node
+
+
+@pytest.mark.skipif(not PY313_PLUS, reason="Type parameter defaults were added in 313")
+def test_type_param_spec_defaults() -> None:
+ node = extract_node("type Alias[**P = [int, str]] = Callable[P, int]")
+ params = node.type_params[0]
+ assert isinstance(params, ParamSpec)
+ assert isinstance(params.name, AssignName)
+ assert params.name.name == "P"
+ assert isinstance(params.default_value, List)
+ assert len(params.default_value.elts) == 2
assert node.inferred()[0] is node
@@ -57,6 +95,23 @@
assert isinstance(params, TypeVarTuple)
assert isinstance(params.name, AssignName)
assert params.name.name == "Ts"
+ assert params.default_value is None
+
+ assert node.inferred()[0] is node
+
+
+@pytest.mark.skipif(not PY313_PLUS, reason="Type parameter defaults were added in 313")
+def test_type_var_tuple_defaults() -> None:
+ node = extract_node("type Alias[*Ts = tuple[int, str]] = tuple[*Ts]")
+ params = node.type_params[0]
+ assert isinstance(params, TypeVarTuple)
+ assert isinstance(params.name, AssignName)
+ assert params.name.name == "Ts"
+ assert isinstance(params.default_value, Subscript)
+ assert isinstance(params.default_value.value, Name)
+ assert params.default_value.value.name == "tuple"
+ assert isinstance(params.default_value.slice, Tuple)
+ assert len(params.default_value.slice.elts) == 2
assert node.inferred()[0] is node
diff --git a/tests/test_utils.py b/tests/test_utils.py
index c06ee58..664b174 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -31,14 +31,12 @@
self.assertEqual(nodes.are_exclusive(xass1, xnames[2]), False)
def test_not_exclusive_walrus_operator(self) -> None:
- node_if, node_body, node_or_else = extract_node(
- """
+ node_if, node_body, node_or_else = extract_node("""
if val := True: #@
print(val) #@
else:
print(val) #@
- """
- )
+ """)
node_if: nodes.If
node_walrus = next(node_if.nodes_of_class(nodes.NamedExpr))
@@ -51,16 +49,14 @@
assert nodes.are_exclusive(node_body, node_or_else) is True
def test_not_exclusive_walrus_multiple(self) -> None:
- node_if, body_1, body_2, or_else_1, or_else_2 = extract_node(
- """
+ node_if, body_1, body_2, or_else_1, or_else_2 = extract_node("""
if (val := True) or (val_2 := True): #@
print(val) #@
print(val_2) #@
else:
print(val) #@
print(val_2) #@
- """
- )
+ """)
node_if: nodes.If
walruses = list(node_if.nodes_of_class(nodes.NamedExpr))
@@ -80,14 +76,12 @@
assert nodes.are_exclusive(walruses[1], or_else_2) is False
def test_not_exclusive_walrus_operator_nested(self) -> None:
- node_if, node_body, node_or_else = extract_node(
- """
+ node_if, node_body, node_or_else = extract_node("""
if all((last_val := i) % 2 == 0 for i in range(10)): #@
print(last_val) #@
else:
print(last_val) #@
- """
- )
+ """)
node_if: nodes.If
node_walrus = next(node_if.nodes_of_class(nodes.NamedExpr))
@@ -100,8 +94,7 @@
assert nodes.are_exclusive(node_body, node_or_else) is True
def test_if(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
if 1:
a = 1
a = 2
@@ -111,8 +104,7 @@
else:
a = 3
a = 4
- """
- )
+ """)
a1 = module.locals["a"][0]
a2 = module.locals["a"][1]
a3 = module.locals["a"][2]
@@ -127,8 +119,7 @@
self.assertEqual(nodes.are_exclusive(a5, a6), False)
def test_try_except(self) -> None:
- module = builder.parse(
- """
+ module = builder.parse("""
try:
def exclusive_func2():
"docstring"
@@ -141,8 +132,7 @@
else:
def exclusive_func2():
"this one redefine the one defined line 42"
- """
- )
+ """)
f1 = module.locals["exclusive_func2"][0]
f2 = module.locals["exclusive_func2"][1]
f3 = module.locals["exclusive_func2"][2]
@@ -159,24 +149,20 @@
self.assertEqual(nodes.are_exclusive(f4, f2), True)
def test_unpack_infer_uninferable_nodes(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
x = [A] * 1
f = [x, [A] * 2]
f
- """
- )
+ """)
inferred = next(node.infer())
unpacked = list(nodes.unpack_infer(inferred))
self.assertEqual(len(unpacked), 3)
self.assertTrue(all(elt is Uninferable for elt in unpacked))
def test_unpack_infer_empty_tuple(self) -> None:
- node = builder.extract_node(
- """
+ node = builder.extract_node("""
()
- """
- )
+ """)
inferred = next(node.infer())
with self.assertRaises(InferenceError):
list(nodes.unpack_infer(inferred))
diff --git a/tests/testdata/python3/data/module.py b/tests/testdata/python3/data/module.py
index af4a75f..98da5dd 100644
--- a/tests/testdata/python3/data/module.py
+++ b/tests/testdata/python3/data/module.py
@@ -2,7 +2,7 @@
"""
__revision__ = '$Id: module.py,v 1.2 2005-11-02 11:56:54 syt Exp $'
-from astroid.nodes.node_classes import Name as NameNode
+from astroid.nodes import Name as NameNode
from astroid import modutils
from astroid.utils import *
import os.path
@@ -59,7 +59,9 @@
return 'hehe'
global_access(local, val=autre)
finally:
- return local
+ # return in finally was previously tested here but became a syntax error
+ # in 3.14 and this file is used in 188/1464 tests
+ a = local
def static_method():
"""static method test"""
diff --git a/tests/testdata/python3/data/module3.14.py b/tests/testdata/python3/data/module3.14.py
new file mode 100644
index 0000000..e5af6b0
--- /dev/null
+++ b/tests/testdata/python3/data/module3.14.py
@@ -0,0 +1,91 @@
+"""test module for astroid
+"""
+
+__revision__ = '$Id: module.py,v 1.2 2005-11-02 11:56:54 syt Exp $'
+from astroid.nodes import Name as NameNode
+from astroid import modutils
+from astroid.utils import *
+import os.path
+MY_DICT = {}
+
+def global_access(key, val):
+ """function test"""
+ local = 1
+ MY_DICT[key] = val
+ for i in val:
+ if i:
+ del MY_DICT[i]
+ continue
+ else:
+ break
+ else:
+ return
+
+
+class YO:
+ """hehe
+ haha"""
+ a = 1
+
+ def __init__(self):
+ try:
+ self.yo = 1
+ except ValueError as ex:
+ pass
+ except (NameError, TypeError):
+ raise XXXError()
+ except:
+ raise
+
+
+
+class YOUPI(YO):
+ class_attr = None
+
+ def __init__(self):
+ self.member = None
+
+ def method(self):
+ """method
+ test"""
+ global MY_DICT
+ try:
+ MY_DICT = {}
+ local = None
+ autre = [a for (a, b) in MY_DICT if b]
+ if b in autre:
+ return
+ elif a in autre:
+ return 'hehe'
+ global_access(local, val=autre)
+ finally:
+ # return in finally was previously tested here but became a syntax error
+ # in 3.14 and is used in 188/1464 tests
+ print(local)
+
+ def static_method():
+ """static method test"""
+ assert MY_DICT, '???'
+ static_method = staticmethod(static_method)
+
+ def class_method(cls):
+ """class method test"""
+ exec(a, b)
+ class_method = classmethod(class_method)
+
+
+def four_args(a, b, c, d):
+ """four arguments (was nested_args)"""
+ while 1:
+ if a:
+ break
+ a += +1
+ else:
+ b += -2
+ if c:
+ d = a and (b or c)
+ else:
+ c = a and b or d
+ list(map(lambda x, y: (y, x), a))
+redirect = four_args
+
diff --git a/tests/testdata/python3/data/x.zip b/tests/testdata/python3/data/x.zip
new file mode 100644
index 0000000..4f663fb
--- /dev/null
+++ b/tests/testdata/python3/data/x.zip
Binary files differ
diff --git a/tests/testdata/python3/pyi_data/find_test/__init__.weird_ext b/tests/testdata/python3/pyi_data/find_test/__init__.weird_ext
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/testdata/python3/pyi_data/find_test/__init__.weird_ext
diff --git a/tests/testdata/python3/pyi_data/find_test/standalone_file.weird_ext b/tests/testdata/python3/pyi_data/find_test/standalone_file.weird_ext
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/testdata/python3/pyi_data/find_test/standalone_file.weird_ext
diff --git a/tox.ini b/tox.ini
index 2f089b6..b3bd055 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py{39,310,311,312,313}
+envlist = py{39,310,311,312,313,314}
skip_missing_interpreters = true
isolated_build = true