diff --git a/.copyrite_aliases b/.copyrite_aliases
deleted file mode 100644
index 91c4615..0000000
--- a/.copyrite_aliases
+++ /dev/null
@@ -1,82 +0,0 @@
-[
-  {
-    "mails": [
-      "cpopa@cloudbasesolutions.com",
-      "pcmanticore@gmail.com"
-    ],
-    "authoritative_mail": "pcmanticore@gmail.com",
-    "name": "Claudiu Popa"
-  },
-  {
-    "mails": [
-      "pierre.sassoulas@gmail.com",
-      "pierre.sassoulas@cea.fr"
-    ],
-    "authoritative_mail": "pierre.sassoulas@gmail.com",
-    "name": "Pierre Sassoulas"
-  },
-  {
-    "mails": [
-        "alexandre.fayolle@logilab.fr",
-        "emile.anclin@logilab.fr",
-        "david.douard@logilab.fr",
-        "laura.medioni@logilab.fr",
-        "anthony.truchet@logilab.fr",
-        "alain.leufroy@logilab.fr",
-        "julien.cristau@logilab.fr",
-        "Adrien.DiMascio@logilab.fr",
-        "emile@crater.logilab.fr",
-        "sylvain.thenault@logilab.fr",
-        "pierre-yves.david@logilab.fr",
-        "nicolas.chauvat@logilab.fr",
-        "afayolle.ml@free.fr",
-        "aurelien.campeas@logilab.fr",
-        "lmedioni@logilab.fr"
-    ],
-    "authoritative_mail": "contact@logilab.fr",
-    "name": "LOGILAB S.A. (Paris, FRANCE)"
-  },
-  {
-    "mails": [
-      "moylop260@vauxoo.com"
-    ],
-    "name": "Moises Lopez",
-    "authoritative_mail": "moylop260@vauxoo.com"
-  },
-  {
-    "mails": [
-       "nathaniel@google.com",
-       "mbp@google.com",
-       "tmarek@google.com",
-       "shlomme@gmail.com",
-       "balparda@google.com",
-       "dlindquist@google.com"
-    ],
-    "name": "Google, Inc."
-  },
-  {
-   "mails": [
-      "ashley@awhetter.co.uk",
-      "awhetter.2011@my.bristol.ac.uk",
-      "asw@dneg.com",
-      "AWhetter@users.noreply.github.com"
-   ],
-   "name": "Ashley Whetter",
-   "authoritative_mail": "ashley@awhetter.co.uk"
-  },
-  {
-    "mails": [
-      "ville.skytta@iki.fi",
-      "ville.skytta@upcloud.com"
-    ],
-    "authoritative_mail": "ville.skytta@iki.fi",
-    "name": "Ville Skyttä"
-  },
-  {
-    "mails": [
-      "66853113+pre-commit-ci[bot]@users.noreply.github.com"
-    ],
-    "authoritative_mail": "bot@noreply.github.com",
-    "name": "pre-commit-ci[bot]"
-  }
-]
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..f58fd12
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# See http://git-scm.com/docs/gitattributes#_end_of_line_conversion
+* text=auto
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..3434a17
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,22 @@
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+  - package-ecosystem: "pip"
+    directory: "/"
+    schedule:
+      interval: "daily"
+    labels:
+      - "dependency"
+    open-pull-requests-limit: 10
+    rebase-strategy: "disabled"
+
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "daily"
+    labels:
+      - "dependency"
+    open-pull-requests-limit: 10
+    rebase-strategy: "disabled"
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 509194e..c5fd39d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -8,7 +8,7 @@ on:
   pull_request: ~
 
 env:
-  CACHE_VERSION: 4
+  CACHE_VERSION: 5
   DEFAULT_PYTHON: 3.8
   PRE_COMMIT_CACHE: ~/.cache/pre-commit
 
@@ -16,17 +16,18 @@ jobs:
   prepare-base:
     name: Prepare base dependencies
     runs-on: ubuntu-latest
+    timeout-minutes: 10
     outputs:
       python-key: ${{ steps.generate-python-key.outputs.key }}
       pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }}
     steps:
       - name: Check out code from GitHub
-        uses: actions/checkout@v2.3.4
+        uses: actions/checkout@v3.0.0
         with:
           fetch-depth: 0
       - name: Set up Python ${{ env.DEFAULT_PYTHON }}
         id: python
-        uses: actions/setup-python@v2.2.1
+        uses: actions/setup-python@v3.0.0
         with:
           python-version: ${{ env.DEFAULT_PYTHON }}
       - name: Generate partial Python venv restore key
@@ -37,7 +38,7 @@ jobs:
           'requirements_test_brain.txt', 'requirements_test_pre_commit.txt') }}"
       - name: Restore Python virtual environment
         id: cache-venv
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: venv
           key: >-
@@ -60,7 +61,7 @@ jobs:
             hashFiles('.pre-commit-config.yaml') }}"
       - name: Restore pre-commit environment
         id: cache-precommit
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: ${{ env.PRE_COMMIT_CACHE }}
           key: >-
@@ -76,18 +77,19 @@ jobs:
   formatting:
     name: Run pre-commit checks
     runs-on: ubuntu-latest
+    timeout-minutes: 10
     needs: prepare-base
     steps:
       - name: Check out code from GitHub
-        uses: actions/checkout@v2.3.4
+        uses: actions/checkout@v3.0.0
       - name: Set up Python ${{ env.DEFAULT_PYTHON }}
         id: python
-        uses: actions/setup-python@v2.2.1
+        uses: actions/setup-python@v3.0.0
         with:
           python-version: ${{ env.DEFAULT_PYTHON }}
       - name: Restore Python virtual environment
         id: cache-venv
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: venv
           key:
@@ -100,7 +102,7 @@ jobs:
           exit 1
       - name: Restore pre-commit environment
         id: cache-precommit
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: ${{ env.PRE_COMMIT_CACHE }}
           key: ${{ runner.os }}-${{ needs.prepare-base.outputs.pre-commit-key }}
@@ -118,6 +120,7 @@ jobs:
   prepare-tests-linux:
     name: Prepare tests for Python ${{ matrix.python-version }} (Linux)
     runs-on: ubuntu-latest
+    timeout-minutes: 10
     strategy:
       matrix:
         python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
@@ -125,12 +128,12 @@ jobs:
       python-key: ${{ steps.generate-python-key.outputs.key }}
     steps:
       - name: Check out code from GitHub
-        uses: actions/checkout@v2.3.4
+        uses: actions/checkout@v3.0.0
         with:
           fetch-depth: 0
       - name: Set up Python ${{ matrix.python-version }}
         id: python
-        uses: actions/setup-python@v2.2.1
+        uses: actions/setup-python@v3.0.0
         with:
           python-version: ${{ matrix.python-version }}
       - name: Generate partial Python venv restore key
@@ -141,7 +144,7 @@ jobs:
           'requirements_test_brain.txt') }}"
       - name: Restore Python virtual environment
         id: cache-venv
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: venv
           key: >-
@@ -161,6 +164,7 @@ jobs:
   pytest-linux:
     name: Run tests Python ${{ matrix.python-version }} (Linux)
     runs-on: ubuntu-latest
+    timeout-minutes: 10
     needs: prepare-tests-linux
     strategy:
       fail-fast: false
@@ -168,15 +172,15 @@ jobs:
         python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
     steps:
       - name: Check out code from GitHub
-        uses: actions/checkout@v2.3.4
+        uses: actions/checkout@v3.0.0
       - name: Set up Python ${{ matrix.python-version }}
         id: python
-        uses: actions/setup-python@v2.2.1
+        uses: actions/setup-python@v3.0.0
         with:
           python-version: ${{ matrix.python-version }}
       - name: Restore Python virtual environment
         id: cache-venv
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: venv
           key:
@@ -192,7 +196,7 @@ jobs:
           . venv/bin/activate
           pytest --cov --cov-report= tests/
       - name: Upload coverage artifact
-        uses: actions/upload-artifact@v2.2.3
+        uses: actions/upload-artifact@v3.0.0
         with:
           name: coverage-${{ matrix.python-version }}
           path: .coverage
@@ -200,6 +204,7 @@ jobs:
   coverage:
     name: Process test coverage
     runs-on: ubuntu-latest
+    timeout-minutes: 5
     needs: ["prepare-tests-linux", "pytest-linux"]
     strategy:
       matrix:
@@ -208,15 +213,15 @@ jobs:
       COVERAGERC_FILE: .coveragerc
     steps:
       - name: Check out code from GitHub
-        uses: actions/checkout@v2.3.4
+        uses: actions/checkout@v3.0.0
       - name: Set up Python ${{ matrix.python-version }}
         id: python
-        uses: actions/setup-python@v2.2.1
+        uses: actions/setup-python@v3.0.0
         with:
           python-version: ${{ matrix.python-version }}
       - name: Restore Python virtual environment
         id: cache-venv
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: venv
           key:
@@ -228,7 +233,7 @@ jobs:
           echo "Failed to restore Python venv from cache"
           exit 1
       - name: Download all coverage artifacts
-        uses: actions/download-artifact@v2.0.9
+        uses: actions/download-artifact@v3.0.0
       - name: Combine coverage results
         run: |
           . venv/bin/activate
@@ -244,6 +249,8 @@ jobs:
   prepare-tests-windows:
     name: Prepare tests for Python ${{ matrix.python-version }} (Windows)
     runs-on: windows-latest
+    timeout-minutes: 10
+    needs: pytest-linux
     strategy:
       matrix:
         python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
@@ -251,12 +258,12 @@ jobs:
       python-key: ${{ steps.generate-python-key.outputs.key }}
     steps:
       - name: Check out code from GitHub
-        uses: actions/checkout@v2.3.4
+        uses: actions/checkout@v3.0.0
         with:
           fetch-depth: 0
       - name: Set up Python ${{ matrix.python-version }}
         id: python
-        uses: actions/setup-python@v2.2.1
+        uses: actions/setup-python@v3.0.0
         with:
           python-version: ${{ matrix.python-version }}
       - name: Generate partial Python venv restore key
@@ -267,7 +274,7 @@ jobs:
           'requirements_test_brain.txt') }}"
       - name: Restore Python virtual environment
         id: cache-venv
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: venv
           key: >-
@@ -287,6 +294,7 @@ jobs:
   pytest-windows:
     name: Run tests Python ${{ matrix.python-version }} (Windows)
     runs-on: windows-latest
+    timeout-minutes: 10
     needs: prepare-tests-windows
     strategy:
       fail-fast: false
@@ -298,15 +306,15 @@ jobs:
         # 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@v2.3.4
+        uses: actions/checkout@v3.0.0
       - name: Set up Python ${{ matrix.python-version }}
         id: python
-        uses: actions/setup-python@v2.2.1
+        uses: actions/setup-python@v3.0.0
         with:
           python-version: ${{ matrix.python-version }}
       - name: Restore Python virtual environment
         id: cache-venv
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: venv
           key:
@@ -325,19 +333,20 @@ jobs:
   prepare-tests-pypy:
     name: Prepare tests for Python ${{ matrix.python-version }}
     runs-on: ubuntu-latest
+    timeout-minutes: 10
     strategy:
       matrix:
-        python-version: ["pypy3"]
+        python-version: ["pypy-3.6", "pypy-3.7", "pypy-3.8"]
     outputs:
       python-key: ${{ steps.generate-python-key.outputs.key }}
     steps:
       - name: Check out code from GitHub
-        uses: actions/checkout@v2.3.4
+        uses: actions/checkout@v3.0.0
         with:
           fetch-depth: 0
       - name: Set up Python ${{ matrix.python-version }}
         id: python
-        uses: actions/setup-python@v2.2.1
+        uses: actions/setup-python@v3.0.0
         with:
           python-version: ${{ matrix.python-version }}
       - name: Generate partial Python venv restore key
@@ -347,14 +356,14 @@ jobs:
             hashFiles('setup.cfg', 'requirements_test_min.txt') }}"
       - name: Restore Python virtual environment
         id: cache-venv
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: venv
           key: >-
-            ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
+            ${{ runner.os }}-${{ matrix.python-version }}-${{
             steps.generate-python-key.outputs.key }}
           restore-keys: |
-            ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ env.CACHE_VERSION }}-
+            ${{ runner.os }}-${{ matrix.python-version }}-venv-${{ env.CACHE_VERSION }}-
       - name: Create Python virtual environment
         if: steps.cache-venv.outputs.cache-hit != 'true'
         run: |
@@ -367,26 +376,27 @@ jobs:
   pytest-pypy:
     name: Run tests Python ${{ matrix.python-version }}
     runs-on: ubuntu-latest
+    timeout-minutes: 10
     needs: prepare-tests-pypy
     strategy:
       fail-fast: false
       matrix:
-        python-version: ["pypy3"]
+        python-version: ["pypy-3.6", "pypy-3.7", "pypy-3.8"]
     steps:
       - name: Check out code from GitHub
-        uses: actions/checkout@v2.3.4
+        uses: actions/checkout@v3.0.0
       - name: Set up Python ${{ matrix.python-version }}
         id: python
-        uses: actions/setup-python@v2.2.1
+        uses: actions/setup-python@v3.0.0
         with:
           python-version: ${{ matrix.python-version }}
       - name: Restore Python virtual environment
         id: cache-venv
-        uses: actions/cache@v2.1.4
+        uses: actions/cache@v2.1.7
         with:
           path: venv
           key:
-            ${{ runner.os }}-${{ steps.python.outputs.python-version }}-${{
+            ${{ runner.os }}-${{ matrix.python-version }}-${{
             needs.prepare-tests-pypy.outputs.python-key }}
       - name: Fail job if Python cache restore failed
         if: steps.cache-venv.outputs.cache-hit != 'true'
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index a3affa8..ab4eef9 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -39,7 +39,7 @@ jobs:
 
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3.0.0
 
       # Initializes the CodeQL tools for scanning.
       - name: Initialize CodeQL
diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml
new file mode 100644
index 0000000..31849ff
--- /dev/null
+++ b/.github/workflows/release-tests.yml
@@ -0,0 +1,61 @@
+name: Release tests
+
+on: workflow_dispatch
+
+env:
+  DEFAULT_PYTHON: 3.8
+
+jobs:
+  virtualenv-15-windows-test:
+    # Regression test added in https://github.com/PyCQA/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@v3.0.0
+      - name: Set up Python
+        id: python
+        uses: actions/setup-python@v3.0.0
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Create Python virtual environment with virtualenv==15.1.0
+        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
+
+  additional-dependencies-linux-tests:
+    name: Regression tests w/ additional dependencies (Linux)
+    runs-on: ubuntu-latest
+    timeout-minutes: 5
+    steps:
+      - name: Check out code from GitHub
+        uses: actions/checkout@v3.0.0
+      - name: Set up Python
+        id: python
+        uses: actions/setup-python@v3.1.0
+        with:
+          python-version: ${{ env.DEFAULT_PYTHON }}
+      - name: Install Qt
+        run: |
+          sudo apt-get install build-essential libgl1-mesa-dev
+      - name: Create Python virtual environment
+        run: |
+          python -m venv venv
+          . venv/bin/activate
+          python -m pip install -U pip setuptools wheel
+          pip install -U -r requirements_test.txt -r requirements_test_brain.txt
+          pip install -e .
+      - name: Run brain_qt tests
+        # Regression test added in https://github.com/PyCQA/astroid/pull/1505
+        run: |
+          . venv/bin/activate
+          pytest tests/unittest_brain_qt.py
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 046af6f..1bf53bf 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,10 +14,10 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code from Github
-        uses: actions/checkout@v2.3.4
+        uses: actions/checkout@v3.0.0
       - name: Set up Python ${{ env.DEFAULT_PYTHON }}
         id: python
-        uses: actions/setup-python@v2.2.2
+        uses: actions/setup-python@v3.0.0
         with:
           python-version: ${{ env.DEFAULT_PYTHON }}
       - name: Install requirements
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6c5d439..53e21f8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,7 +3,7 @@ ci:
 
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.1.0
+    rev: v4.2.0
     hooks:
       - id: trailing-whitespace
         exclude: .github/|tests/testdata
@@ -20,8 +20,15 @@ repos:
           - --expand-star-imports
           - --remove-duplicate-keys
           - --remove-unused-variables
+  - repo: https://github.com/Pierre-Sassoulas/copyright_notice_precommit
+    rev: 0.1.2
+    hooks:
+      - id: copyright-notice
+        args: ["--notice=script/copyright.txt", "--enforce-all", "--autofix"]
+        exclude: tests/testdata|setup.py
+        types: [python]
   - repo: https://github.com/asottile/pyupgrade
-    rev: v2.31.0
+    rev: v2.32.0
     hooks:
       - id: pyupgrade
         exclude: tests/testdata
@@ -37,7 +44,7 @@ repos:
       - id: black-disable-checker
         exclude: tests/unittest_nodes_lineno.py
   - repo: https://github.com/psf/black
-    rev: 21.12b0
+    rev: 22.3.0
     hooks:
       - id: black
         args: [--safe, --quiet]
@@ -46,8 +53,9 @@ repos:
     rev: 4.0.1
     hooks:
       - id: flake8
-        additional_dependencies: [flake8-bugbear, flake8-typing-imports==1.11.0]
-        exclude: tests/testdata|doc/conf.py|astroid/__init__.py
+        additional_dependencies:
+          [flake8-bugbear==22.3.23, flake8-typing-imports==1.12.0]
+        exclude: tests/testdata|doc/conf.py
   - repo: local
     hooks:
       - id: pylint
@@ -63,7 +71,7 @@ repos:
           ]
         exclude: tests/testdata|conf.py
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.930
+    rev: v0.942
     hooks:
       - id: mypy
         name: mypy
@@ -74,7 +82,7 @@ repos:
         require_serial: true
         additional_dependencies:
           [
-            "types-pkg_resources==0.1.2",
+            "types-pkg_resources==0.1.3",
             "types-six",
             "types-attrs",
             "types-python-dateutil",
@@ -82,7 +90,7 @@ repos:
           ]
         exclude: tests/testdata| # exclude everything, we're not ready
   - repo: https://github.com/pre-commit/mirrors-prettier
-    rev: v2.5.1
+    rev: v2.6.2
     hooks:
       - id: prettier
         args: [--prose-wrap=always, --print-width=88]
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
new file mode 100644
index 0000000..fb57482
--- /dev/null
+++ b/CONTRIBUTORS.txt
@@ -0,0 +1,176 @@
+# This file is autocompleted by 'contributors-txt',
+# using the configuration in 'script/.contributors_aliases.json'.
+# Do not add new persons manually and only add information without
+# using '-' as the line first character.
+# Please verify that your change are stable if you modify manually.
+
+Ex-maintainers
+--------------
+- Claudiu Popa <pcmanticore@gmail.com>
+- Sylvain Thénault <thenault@gmail.com>
+- Torsten Marek <shlomme@gmail.com>
+
+
+Maintainers
+-----------
+- Pierre Sassoulas <pierre.sassoulas@gmail.com>
+- Hippo91 <guillaume.peillex@gmail.com>
+- Marc Mueller <30130371+cdce8p@users.noreply.github.com>
+- Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
+- Bryce Guinta <bryce.paul.guinta@gmail.com>
+- Ceridwen <ceridwenv@gmail.com>
+- Łukasz Rogalski <rogalski.91@gmail.com>
+- Florian Bruhin <me@the-compiler.org>
+- Ashley Whetter <ashley@awhetter.co.uk>
+- Jacob Walls <jacobtylerwalls@gmail.com>
+- Dimitri Prybysh <dmand@yandex.ru>
+- Areveny <areveny@protonmail.com>
+
+
+Contributors
+------------
+- LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
+- Nick Drozd <nicholasdrozd@gmail.com>
+- Andrew Haigh <hello@nelf.in>
+- David Liu <david@cs.toronto.edu>
+- Eevee (Alex Munroe) <amunroe@yelp.com>
+- David Gilman <davidgilman1@gmail.com>
+- Julien Jehannet <julien.jehannet@logilab.fr>
+- Calen Pennington <calen.pennington@gmail.com>
+- Phil Schaf <flying-sheep@web.de>
+- Hugo van Kemenade <hugovk@users.noreply.github.com>
+- Alex Hall <alex.mojaki@gmail.com>
+- Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
+- Tim Martin <tim@asymptotic.co.uk>
+- Raphael Gaschignard <raphael@makeleaps.com>
+- Radosław Ganczarek <radoslaw@ganczarek.in>
+- Paligot Gérard <androwiiid@gmail.com>
+- Ioana Tagirta <ioana.tagirta@gmail.com>
+- Derek Gustafson <degustaf@gmail.com>
+- David Shea <dshea@redhat.com>
+- Daniel Harding <dharding@gmail.com>
+- Ville Skyttä <ville.skytta@iki.fi>
+- Rene Zhang <rz99@cornell.edu>
+- Philip Lorenz <philip@bithub.de>
+- Mario Corchero <mariocj89@gmail.com>
+- Marien Zwart <marienz@gentoo.org>
+- FELD Boris <lothiraldan@gmail.com>
+- Enji Cooper <yaneurabeya@gmail.com>
+- doranid <ddandd@gmail.com>
+- brendanator <brendan.maginnis@gmail.com>
+- Tomas Gavenciak <gavento@ucw.cz>
+- Thomas Hisch <t.hisch@gmail.com>
+- Stefan Scherfke <stefan@sofa-rockers.org>
+- Sergei Lebedev <185856+superbobry@users.noreply.github.com>
+- Ram Rachum <ram@rachum.com>
+- Peter Pentchev <roam@ringlet.net>
+- Peter Kolbus <peter.kolbus@gmail.com>
+- Omer Katz <omer.drow@gmail.com>
+- Moises Lopez <moylop260@vauxoo.com>
+- Michael <michael-k@users.noreply.github.com>
+- Keichi Takahashi <keichi.t@me.com>
+- Kavins Singh <kavinsingh@hotmail.com>
+- Karthikeyan Singaravelan <tir.karthi@gmail.com>
+- Joshua Cannon <joshdcannon@gmail.com>
+- John Vandenberg <jayvdb@gmail.com>
+- Jacob Bogdanov <jacob@bogdanov.dev>
+- Google, Inc. <no-reply@google.com>
+- David Euresti <github@euresti.com>
+- David Cain <davidjosephcain@gmail.com>
+- Anthony Sottile <asottile@umich.edu>
+- Alexander Shadchin <alexandr.shadchin@gmail.com>
+- wgehalo <wgehalo@gmail.com>
+- tristanlatr <19967168+tristanlatr@users.noreply.github.com>
+- rr- <rr-@sakuya.pl>
+- raylu <lurayl@gmail.com>
+- mathieui <mathieui@users.noreply.github.com>
+- markmcclain <markmcclain@users.noreply.github.com>
+- ioanatia <ioanatia@users.noreply.github.com>
+- grayjk <grayjk@gmail.com>
+- Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
+- Zac Hatfield-Dodds <Zac-HD@users.noreply.github.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>
+- Stanislav Levin <slev@altlinux.org>
+- Simon Hewitt <si@sjhewitt.co.uk>
+- Serhiy Storchaka <storchaka@gmail.com>
+- Roy Wright <roy@wright.org>
+- Robin Jarry <robin.jarry@6wind.com>
+- René Fritze <47802+renefritze@users.noreply.github.com>
+- Redoubts <Redoubts@users.noreply.github.com>
+- Philipp Hörist <philipp@hoerist.com>
+- Peter de Blanc <peter@standard.ai>
+- Peter Talley <peterctalley@gmail.com>
+- Ovidiu Sabou <ovidiu@sabou.org>
+- Nicolas Noirbent <nicolas@noirbent.fr>
+- Neil Girdhar <mistersheik@gmail.com>
+- Michał Masłowski <m.maslowski@clearcode.cc>
+- Michael K <michael-k@users.noreply.github.com>
+- Mateusz Bysiek <mb@mbdev.pl>
+- Mark Byrne <31762852+mbyrnepr2@users.noreply.github.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>
+- Jonathan Striebel <jstriebel@users.noreply.github.com>
+- John Belmonte <john@neggie.net>
+- Jeff Widman <jeff@jeffwidman.com>
+- Jeff Quast <contact@jeffquast.com>
+- Jarrad Hope <me@jarradhope.com>
+- Jared Garst <jgarst@users.noreply.github.com>
+- Jakub Wilk <jwilk@jwilk.net>
+- Iva Miholic <ivamiho@gmail.com>
+- Ionel Maries Cristian <contact@ionelmc.ro>
+- HoverHell <hoverhell@gmail.com>
+- HQupgradeHQ <18361586+HQupgradeHQ@users.noreply.github.com>
+- Grygorii Iermolenko <gyermolenko@gmail.com>
+- Gregory P. Smith <greg@krypto.org>
+- Giuseppe Scrivano <gscrivan@redhat.com>
+- Frédéric Chapoton <fchapoton2@gmail.com>
+- Francis Charette Migneault <francis.charette.migneault@gmail.com>
+- Felix Mölder <felix.moelder@uni-due.de>
+- Federico Bond <federicobond@gmail.com>
+- DudeNr33 <3929834+DudeNr33@users.noreply.github.com>
+- Dmitry Shachnev <mitya57@users.noreply.github.com>
+- Denis Laxalde <denis.laxalde@logilab.fr>
+- David Poirier <david-poirier-csn@users.noreply.github.com>
+- Dave Hirschfeld <dave.hirschfeld@gmail.com>
+- Dave Baum <dbaum@google.com>
+- Daniel Martin <daniel.martin@crowdstrike.com>
+- Daniel Colascione <dancol@dancol.org>
+- Damien Baty <damien@damienbaty.com>
+- Craig Franklin <craigjfranklin@gmail.com>
+- Colin Kennedy <colinvfx@gmail.com>
+- Cole Robinson <crobinso@redhat.com>
+- Christoph Reiter <reiter.christoph@gmail.com>
+- Chris Philip <chrisp533@gmail.com>
+- BioGeek <jeroen.vangoey@gmail.com>
+- Bianca Power <30207144+biancapower@users.noreply.github.com>
+- Benjamin Elven <25181435+S3ntinelX@users.noreply.github.com>
+- Becker Awqatty <bawqatty@mide.com>
+- BasPH <BasPH@users.noreply.github.com>
+- Azeem Bande-Ali <A.BandeAli@gmail.com>
+- Aru Sahni <arusahni@gmail.com>
+- Artsiom Kaval <lezeroq@gmail.com>
+- Anubhav <35621759+anubh-v@users.noreply.github.com>
+- Antoine Boellinger <aboellinger@hotmail.com>
+- Alphadelta14 <alpha@alphaservcomputing.solutions>
+- Alexander Presnyakov <flagist0@gmail.com>
+- Ahmed Azzaoui <ahmed.azzaoui@engie.com>
+
+Co-Author
+---------
+The following persons were credited manually but did not commit themselves
+under this name, or we did not manage to find their commits in the history.
+
+- François Mockers
+- platings
+- carl
+- alain lefroy
+- Mark Gius
+- jarradhope
diff --git a/ChangeLog b/ChangeLog
index 3bb23e2..736f897 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,17 +2,155 @@
 astroid's ChangeLog
 ===================
 
-What's New in astroid 2.10.0?
+What's New in astroid 2.12.0?
 =============================
 Release date: TBA
 
 
-What's New in astroid 2.9.4?
-============================
+
+What's New in astroid 2.11.4?
+=============================
 Release date: TBA
 
 
 
+What's New in astroid 2.11.3?
+=============================
+Release date: 2022-04-19
+
+* Fixed an error in the Qt brain when building ``instance_attrs``.
+
+  Closes PyCQA/pylint#6221
+
+* Fixed a crash in the ``gi`` brain.
+
+  Closes PyCQA/pylint#6371
+
+
+What's New in astroid 2.11.2?
+=============================
+Release date: 2022-03-26
+
+* Avoided adding the name of a parent namedtuple to its child's locals.
+
+  Refs PyCQA/pylint#5982
+
+
+What's New in astroid 2.11.1?
+=============================
+Release date: 2022-03-22
+
+* Promoted ``getattr()`` from ``astroid.scoped_nodes.FunctionDef`` to its parent
+  ``astroid.scoped_nodes.Lambda``.
+
+* Fixed crash on direct inference via ``nodes.FunctionDef._infer``.
+
+  Closes #817
+
+
+What's New in astroid 2.11.0?
+=============================
+Release date: 2022-03-12
+
+* Add new (optional) ``doc_node`` attribute to ``nodes.Module``, ``nodes.ClassDef``,
+  and ``nodes.FunctionDef``.
+
+* Accessing the ``doc`` attribute of ``nodes.Module``, ``nodes.ClassDef``, and
+  ``nodes.FunctionDef`` has been deprecated in favour of the ``doc_node`` attribute.
+  Note: ``doc_node`` is an (optional) ``nodes.Const`` whereas ``doc`` was an (optional) ``str``.
+
+* Passing the ``doc`` argument to the ``__init__`` of ``nodes.Module``, ``nodes.ClassDef``,
+  and ``nodes.FunctionDef`` has been deprecated in favour of the ``postinit`` ``doc_node`` attribute.
+  Note: ``doc_node`` is an (optional) ``nodes.Const`` whereas ``doc`` was an (optional) ``str``.
+
+* Replace custom ``cachedproperty`` with ``functools.cached_property`` and deprecate it
+  for Python 3.8+.
+
+  Closes #1410
+
+* Set ``end_lineno`` and ``end_col_offset`` attributes to ``None`` for all nodes
+  with PyPy 3.8. PyPy 3.8 assigns these attributes inconsistently which could lead
+  to unexpected errors. Overwriting them with ``None`` will cause a fallback
+  to the already supported way of PyPy 3.7.
+
+* Add missing ``shape`` parameter to numpy ``zeros_like``, ``ones_like``,
+  and ``full_like`` methods.
+
+  Closes PyCQA/pylint#5871
+
+* Only pin ``wrapt`` on the major version.
+
+
+What's New in astroid 2.10.0?
+=============================
+Release date: 2022-02-27
+
+
+* Fixed inference of ``self`` in binary operations in which ``self``
+  is part of a list or tuple.
+
+  Closes PyCQA/pylint#4826
+
+* Fixed builtin inference on `property` calls not calling the `postinit` of the new node, which
+  resulted in instance arguments missing on these nodes.
+
+* Fixed a crash on ``Super.getattr`` when the attribute was previously uninferable due to a cache
+  limit size. This limit can be hit when the inheritance pattern of a class (and therefore of the
+  ``__init__`` attribute) is very large.
+
+  Closes PyCQA/pylint#5679
+
+* Inlcude names of keyword-only arguments in ``astroid.scoped_nodes.Lambda.argnames``.
+
+  Closes PyCQA/pylint#5771
+
+* Fixed a crash inferring on a ``NewType`` named with an f-string.
+
+  Closes PyCQA/pylint#5770
+
+* Add support for [attrs v21.3.0](https://github.com/python-attrs/attrs/releases/tag/21.3.0) which
+  added a new `attrs` module alongside the existing `attr`.
+
+  Closes #1330
+
+* Use the ``end_lineno`` attribute for the ``NodeNG.tolineno`` property
+  when it is available.
+
+  Closes #1350
+
+* Add ``is_dataclass`` attribute to ``ClassDef`` nodes.
+
+* Use ``sysconfig`` instead of ``distutils`` to determine the location of
+  python stdlib files and packages.
+
+  Related pull requests: #1322, #1323, #1324
+  Closes #1282
+  Ref #1103
+
+* Fixed crash with recursion error for inference of class attributes that referenced
+  the class itself.
+
+  Closes PyCQA/pylint#5408
+
+* Fixed crash when trying to infer ``items()`` on the ``__dict__``
+  attribute of an imported module.
+
+  Closes #1085
+
+* Add optional ``NodeNG.position`` attribute.
+  Used for block nodes to highlight position of keyword(s) and name
+  in cases where the AST doesn't provide good enough positional information.
+  E.g. ``nodes.ClassDef``, ``nodes.FunctionDef``.
+
+* Fix ``ClassDef.fromlineno``. For Python < 3.8 the ``lineno`` attribute includes decorators.
+  ``fromlineno`` should return the line of the ``class`` statement itself.
+
+* Performance improvements. Only run expensive decorator functions when
+  non-default Deprecation warnings are enabled, eg. during a Pytest run.
+
+  Closes #1383
+
+
 What's New in astroid 2.9.3?
 ============================
 Release date: 2022-01-09
@@ -51,6 +189,10 @@ Release date: 2021-12-31
 
   Ref #1321
 
+* Restore custom ``distutils`` handling for resolving paths to submodules.
+
+  Closes PyCQA/pylint#5645
+
 * Fix ``deque.insert()`` signature in ``collections`` brain.
 
   Closes #1260
@@ -60,7 +202,8 @@ Release date: 2021-12-31
 
 * Fix typing and update explanation for ``Arguments.args`` being ``None``.
 
-* Fix crash if a variable named ``type`` is subscripted in a generator expression.
+* Fix crash if a variable named ``type`` is accessed with an index operator (``[]``)
+  in a generator expression.
 
   Closes PyCQA/pylint#5461
 
@@ -341,11 +484,11 @@ Release date: 2021-08-03
 
 * Added support to infer return type of ``typing.cast()``
 
-* Fix variable lookup's handling of exclusive statements
+* Fix variable lookup handling of exclusive statements
 
   Closes PyCQA/pylint#3711
 
-* Fix variable lookup's handling of function parameters
+* Fix variable lookup handling of function parameters
 
   Closes PyCQA/astroid#180
 
@@ -385,7 +528,7 @@ Release date: 2021-07-19
 
 * Added ``If.is_sys_guard`` and ``If.is_typing_guard`` helper methods
 
-* Fix a bad inferenece type for yield values inside of a derived class.
+* Fix a bad inference type for yield values inside of a derived class.
 
   Closes PyCQA/astroid#1090
 
@@ -1420,7 +1563,7 @@ Release date: 2018-07-15
 
    * Fix missing __module__ and __qualname__ from class definition locals
 
-     Close PYCQA/pylint#1753
+     Close PyCQA/pylint#1753
 
    * Fix a crash when __annotations__ access a parent's __init__ that does not have arguments
 
@@ -1507,7 +1650,7 @@ Release date: 2017-12-15
 
    * Add brain tip for attrs library to prevent unsupported-assignment-operation false positives
 
-	 Close PYCQA/pylint#1698
+	 Close PyCQA/pylint#1698
 
    * file_stream was removed, since it was deprecated for three releases
 
@@ -1925,7 +2068,7 @@ Release date: 2015-11-29
     * Add basic support for understanding context managers.
 
       Currently, there's no way to understand whatever __enter__ returns in a
-      context manager and what it is binded using the ``as`` keyword. With these changes,
+      context manager and what it is bound using the ``as`` keyword. With these changes,
       we can understand ``bar`` in ``with foo() as bar``, which will be the result of __enter__.
 
     * Add a new type of node, called *inference objects*. Inference objects are similar with
diff --git a/astroid/__init__.py b/astroid/__init__.py
index a16a281..14a61c2 100644
--- a/astroid/__init__.py
+++ b/astroid/__init__.py
@@ -1,19 +1,6 @@
-# Copyright (c) 2006-2013, 2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Python Abstract Syntax Tree New Generation
 
@@ -22,8 +9,11 @@ python source code for projects such as pychecker, pyreverse,
 pylint... Well, actually the development of this library is essentially
 governed by pylint's needs.
 
-It extends class defined in the python's _ast module with some
-additional methods and attributes. Instance attributes are added by a
+It mimics the class defined in the python's _ast module with some
+additional methods and attributes. New nodes instances are not fully
+compatible with python's _ast.
+
+Instance attributes are added by a
 builder object, which can either generate extended ast (let's call
 them astroid ;) by visiting an existent ast tree or by inspecting living
 object. Methods are added by monkey patching ast classes.
@@ -40,6 +30,8 @@ Main modules are:
 * builder contains the class responsible to build astroid trees
 """
 
+import functools
+import tokenize
 from importlib import import_module
 from pathlib import Path
 
@@ -57,8 +49,37 @@ from astroid.astroid_manager import MANAGER
 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 Context, Del, Load, Store
-from astroid.exceptions import *
+from astroid.const import PY310_PLUS, Context, Del, Load, Store
+from astroid.exceptions import (
+    AstroidBuildingError,
+    AstroidBuildingException,
+    AstroidError,
+    AstroidImportError,
+    AstroidIndexError,
+    AstroidSyntaxError,
+    AstroidTypeError,
+    AstroidValueError,
+    AttributeInferenceError,
+    BinaryOperationError,
+    DuplicateBasesError,
+    InconsistentMroError,
+    InferenceError,
+    InferenceOverwriteError,
+    MroError,
+    NameInferenceError,
+    NoDefault,
+    NotFoundError,
+    OperationError,
+    ParentMissingError,
+    ResolveError,
+    StatementMissing,
+    SuperArgumentTypeError,
+    SuperError,
+    TooManyLevelsError,
+    UnaryOperationError,
+    UnresolvableName,
+    UseInferenceDefault,
+)
 from astroid.inference_tip import _inference_tip_cached, inference_tip
 from astroid.objects import ExceptionInstance
 
@@ -162,6 +183,15 @@ from astroid.nodes import (  # pylint: disable=redefined-builtin (Ellipsis)
 
 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]
+
 # load brain plugins
 ASTROID_INSTALL_DIRECTORY = Path(__file__).parent
 BRAIN_MODULES_DIRECTORY = ASTROID_INSTALL_DIRECTORY / "brain"
diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py
index 849e09d..c8ee657 100644
--- a/astroid/__pkginfo__.py
+++ b/astroid/__pkginfo__.py
@@ -1,28 +1,6 @@
-# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2017 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2015 Radosław Ganczarek <radoslaw@ganczarek.in>
-# Copyright (c) 2016 Moises Lopez <moylop260@vauxoo.com>
-# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 Calen Pennington <cale@edx.org>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019 Uilian Ries <uilianries@gmail.com>
-# Copyright (c) 2019 Thomas Hisch <t.hisch@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2020 Konrad Weihmann <kweihmann@outlook.com>
-# Copyright (c) 2020 Felix Mölder <felix.moelder@uni-due.de>
-# Copyright (c) 2020 Michael <michael-k@users.noreply.github.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
-__version__ = "2.9.3"
+__version__ = "2.11.3"
 version = __version__
diff --git a/astroid/_ast.py b/astroid/_ast.py
index c570eaa..1c4da43 100644
--- a/astroid/_ast.py
+++ b/astroid/_ast.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import ast
 import sys
 import types
diff --git a/astroid/arguments.py b/astroid/arguments.py
index a34e1b9..40061d0 100644
--- a/astroid/arguments.py
+++ b/astroid/arguments.py
@@ -1,21 +1,11 @@
-# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2020 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-from typing import Optional
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
+from typing import Optional, Set
 
 from astroid import nodes
 from astroid.bases import Instance
-from astroid.const import Context
 from astroid.context import CallContext, InferenceContext
 from astroid.exceptions import InferenceError, NoDefault
 from astroid.util import Uninferable
@@ -46,7 +36,7 @@ class CallSite:
         self.argument_context_map = argument_context_map
         args = callcontext.args
         keywords = callcontext.keywords
-        self.duplicated_keywords = set()
+        self.duplicated_keywords: Set[str] = set()
         self._unpacked_args = self._unpack_args(args, context=context)
         self._unpacked_kwargs = self._unpack_keywords(keywords, context=context)
 
@@ -60,7 +50,7 @@ class CallSite:
         }
 
     @classmethod
-    def from_call(cls, call_node, context: Optional[Context] = None):
+    def from_call(cls, call_node, context: Optional[InferenceContext] = None):
         """Get a CallSite object from the given Call node.
 
         context will be used to force a single inference path.
diff --git a/astroid/astroid_manager.py b/astroid/astroid_manager.py
index c8237a5..da51de7 100644
--- a/astroid/astroid_manager.py
+++ b/astroid/astroid_manager.py
@@ -8,7 +8,7 @@ AstroidManager() directly.
 
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 from astroid.manager import AstroidManager
 
diff --git a/astroid/bases.py b/astroid/bases.py
index 4b5114e..5adaf51 100644
--- a/astroid/bases.py
+++ b/astroid/bases.py
@@ -1,29 +1,6 @@
-# Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2016-2017 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2017 Calen Pennington <calen.pennington@gmail.com>
-# Copyright (c) 2018-2019 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018-2019 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Daniel Colascione <dancol@dancol.org>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 doranid <ddandd@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """This module contains base classes and functions for the nodes and some
 inference utils.
diff --git a/astroid/brain/brain_argparse.py b/astroid/brain/brain_argparse.py
index de36e89..ee0127c 100644
--- a/astroid/brain/brain_argparse.py
+++ b/astroid/brain/brain_argparse.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 from astroid import arguments, inference_tip, nodes
 from astroid.exceptions import UseInferenceDefault
 from astroid.manager import AstroidManager
@@ -9,8 +13,7 @@ def infer_namespace(node, context=None):
         # Cannot make sense of it.
         raise UseInferenceDefault()
 
-    class_node = nodes.ClassDef("Namespace", "docstring")
-    class_node.parent = node.parent
+    class_node = nodes.ClassDef("Namespace", parent=node.parent)
     for attr in set(callsite.keyword_arguments):
         fake_node = nodes.EmptyNode()
         fake_node.parent = class_node
diff --git a/astroid/brain/brain_attrs.py b/astroid/brain/brain_attrs.py
index 65e897c..32b8ce0 100644
--- a/astroid/brain/brain_attrs.py
+++ b/astroid/brain/brain_attrs.py
@@ -1,5 +1,7 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """
 Astroid hook for the attrs library
 
@@ -10,7 +12,9 @@ from astroid.manager import AstroidManager
 from astroid.nodes.node_classes import AnnAssign, Assign, AssignName, Call, Unknown
 from astroid.nodes.scoped_nodes import ClassDef
 
-ATTRIB_NAMES = frozenset(("attr.ib", "attrib", "attr.attrib", "attr.field", "field"))
+ATTRIB_NAMES = frozenset(
+    ("attr.ib", "attrib", "attr.attrib", "attr.field", "attrs.field", "field")
+)
 ATTRS_NAMES = frozenset(
     (
         "attr.s",
@@ -20,6 +24,9 @@ ATTRS_NAMES = frozenset(
         "attr.define",
         "attr.mutable",
         "attr.frozen",
+        "attrs.define",
+        "attrs.mutable",
+        "attrs.frozen",
     )
 )
 
diff --git a/astroid/brain/brain_boto3.py b/astroid/brain/brain_boto3.py
index 27247a3..54faa64 100644
--- a/astroid/brain/brain_boto3.py
+++ b/astroid/brain/brain_boto3.py
@@ -1,5 +1,6 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for understanding boto3.ServiceRequest()"""
 from astroid import extract_node
diff --git a/astroid/brain/brain_builtin_inference.py b/astroid/brain/brain_builtin_inference.py
index 0bf3526..5d7040a 100644
--- a/astroid/brain/brain_builtin_inference.py
+++ b/astroid/brain/brain_builtin_inference.py
@@ -1,30 +1,15 @@
-# Copyright (c) 2014-2021 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Rene Zhang <rz99@cornell.edu>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2019-2020 Bryce Guinta <bryce.guinta@protonmail.com>
-# Copyright (c) 2019 Stanislav Levin <slev@altlinux.org>
-# Copyright (c) 2019 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2019 Frédéric Chapoton <fchapoton2@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2020 Ram Rachum <ram@rachum.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for various builtins."""
 
 from functools import partial
+from typing import Optional
 
 from astroid import arguments, helpers, inference_tip, nodes, objects, util
 from astroid.builder import AstroidBuilder
+from astroid.context import InferenceContext
 from astroid.exceptions import (
     AstroidTypeError,
     AttributeInferenceError,
@@ -547,7 +532,9 @@ def infer_callable(node, context=None):
     return nodes.Const(inferred.callable())
 
 
-def infer_property(node, context=None):
+def infer_property(
+    node: nodes.Call, context: Optional[InferenceContext] = None
+) -> objects.Property:
     """Understand `property` class
 
     This only infers the output of `property`
@@ -566,14 +553,19 @@ def infer_property(node, context=None):
     if not isinstance(inferred, (nodes.FunctionDef, nodes.Lambda)):
         raise UseInferenceDefault
 
-    return objects.Property(
+    prop_func = objects.Property(
         function=inferred,
         name=inferred.name,
-        doc=getattr(inferred, "doc", None),
         lineno=node.lineno,
         parent=node,
         col_offset=node.col_offset,
     )
+    prop_func.postinit(
+        body=[],
+        args=inferred.args,
+        doc_node=getattr(inferred, "doc_node", None),
+    )
+    return prop_func
 
 
 def infer_bool(node, context=None):
diff --git a/astroid/brain/brain_collections.py b/astroid/brain/brain_collections.py
index 5fcebec..43304ec 100644
--- a/astroid/brain/brain_collections.py
+++ b/astroid/brain/brain_collections.py
@@ -1,15 +1,6 @@
-# Copyright (c) 2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016-2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2018 Ioana Tagirta <ioana.tagirta@gmail.com>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 John Belmonte <john@neggie.net>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 from astroid.brain.helpers import register_module_extender
 from astroid.builder import extract_node, parse
diff --git a/astroid/brain/brain_crypt.py b/astroid/brain/brain_crypt.py
index b0ed9ce..45c3055 100644
--- a/astroid/brain/brain_crypt.py
+++ b/astroid/brain/brain_crypt.py
@@ -1,5 +1,7 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 from astroid.brain.helpers import register_module_extender
 from astroid.builder import parse
 from astroid.const import PY37_PLUS
diff --git a/astroid/brain/brain_ctypes.py b/astroid/brain/brain_ctypes.py
index 493b0be..323b19c 100644
--- a/astroid/brain/brain_ctypes.py
+++ b/astroid/brain/brain_ctypes.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """
 Astroid hooks for ctypes module.
 
diff --git a/astroid/brain/brain_curses.py b/astroid/brain/brain_curses.py
index f623e2b..66cd5b2 100644
--- a/astroid/brain/brain_curses.py
+++ b/astroid/brain/brain_curses.py
@@ -1,5 +1,7 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 from astroid.brain.helpers import register_module_extender
 from astroid.builder import parse
 from astroid.manager import AstroidManager
diff --git a/astroid/brain/brain_dataclasses.py b/astroid/brain/brain_dataclasses.py
index bfdbbe0..769d9ee 100644
--- a/astroid/brain/brain_dataclasses.py
+++ b/astroid/brain/brain_dataclasses.py
@@ -1,5 +1,7 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """
 Astroid hook for the dataclasses library
 
@@ -67,6 +69,7 @@ def is_decorated_with_dataclass(node, decorator_names=DATACLASSES_DECORATORS):
 
 def dataclass_transform(node: ClassDef) -> None:
     """Rewrite a dataclass to be easily understood by pylint"""
+    node.is_dataclass = True
 
     for assign_node in _get_dataclass_attributes(node):
         name = assign_node.target.name
diff --git a/astroid/brain/brain_dateutil.py b/astroid/brain/brain_dateutil.py
index 11ae3bc..0d27135 100644
--- a/astroid/brain/brain_dateutil.py
+++ b/astroid/brain/brain_dateutil.py
@@ -1,12 +1,6 @@
-# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015 raylu <lurayl@gmail.com>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for dateutil"""
 
diff --git a/astroid/brain/brain_fstrings.py b/astroid/brain/brain_fstrings.py
index 4eea455..db7dd95 100644
--- a/astroid/brain/brain_fstrings.py
+++ b/astroid/brain/brain_fstrings.py
@@ -1,11 +1,7 @@
-# Copyright (c) 2017-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Karthikeyan Singaravelan <tir.karthi@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import collections.abc
 
 from astroid.manager import AstroidManager
diff --git a/astroid/brain/brain_functools.py b/astroid/brain/brain_functools.py
index 2126853..63333dc 100644
--- a/astroid/brain/brain_functools.py
+++ b/astroid/brain/brain_functools.py
@@ -1,14 +1,14 @@
-# Copyright (c) 2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2018 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Alphadelta14 <alpha@alphaservcomputing.solutions>
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for understanding functools library module."""
 from functools import partial
 from itertools import chain
+from typing import Iterator, Optional
 
-from astroid import BoundMethod, arguments, extract_node, helpers, objects
+from astroid import BoundMethod, arguments, extract_node, helpers, nodes, objects
+from astroid.context import InferenceContext
 from astroid.exceptions import InferenceError, UseInferenceDefault
 from astroid.inference_tip import inference_tip
 from astroid.interpreter import objectmodel
@@ -62,7 +62,9 @@ def _transform_lru_cache(node, context=None) -> None:
     node.special_attributes = LruWrappedModel()(node)
 
 
-def _functools_partial_inference(node, context=None):
+def _functools_partial_inference(
+    node: nodes.Call, context: Optional[InferenceContext] = None
+) -> Iterator[objects.PartialFunction]:
     call = arguments.CallSite.from_call(node, context=context)
     number_of_positional = len(call.positional_arguments)
     if number_of_positional < 1:
@@ -101,7 +103,6 @@ def _functools_partial_inference(node, context=None):
     partial_function = objects.PartialFunction(
         call,
         name=inferred_wrapped_function.name,
-        doc=inferred_wrapped_function.doc,
         lineno=inferred_wrapped_function.lineno,
         col_offset=inferred_wrapped_function.col_offset,
         parent=node.parent,
@@ -113,6 +114,7 @@ def _functools_partial_inference(node, context=None):
         returns=inferred_wrapped_function.returns,
         type_comment_returns=inferred_wrapped_function.type_comment_returns,
         type_comment_args=inferred_wrapped_function.type_comment_args,
+        doc_node=inferred_wrapped_function.doc_node,
     )
     return iter((partial_function,))
 
diff --git a/astroid/brain/brain_gi.py b/astroid/brain/brain_gi.py
index 86b6f9c..5728e2d 100644
--- a/astroid/brain/brain_gi.py
+++ b/astroid/brain/brain_gi.py
@@ -1,20 +1,6 @@
-# Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2014 Cole Robinson <crobinso@redhat.com>
-# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 David Shea <dshea@redhat.com>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2016 Giuseppe Scrivano <gscrivan@redhat.com>
-# Copyright (c) 2018 Christoph Reiter <reiter.christoph@gmail.com>
-# Copyright (c) 2019 Philipp Hörist <philipp@hoerist.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for the Python 2 GObject introspection bindings.
 
@@ -88,7 +74,9 @@ def _gi_build_stub(parent):
 
         try:
             obj = getattr(parent, name)
-        except AttributeError:
+        except Exception:  # pylint: disable=broad-except
+            # gi.module.IntrospectionModule.__getattr__() can raise all kinds of things
+            # like ValueError, TypeError, NotImplementedError, RepositoryError, etc
             continue
 
         if inspect.isclass(obj):
diff --git a/astroid/brain/brain_hashlib.py b/astroid/brain/brain_hashlib.py
index 3671490..094e2ab 100644
--- a/astroid/brain/brain_hashlib.py
+++ b/astroid/brain/brain_hashlib.py
@@ -1,14 +1,6 @@
-# Copyright (c) 2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2018 David Poirier <david-poirier-csn@users.noreply.github.com>
-# Copyright (c) 2018 wgehalo <wgehalo@gmail.com>
-# Copyright (c) 2018 Ioana Tagirta <ioana.tagirta@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 from astroid.brain.helpers import register_module_extender
 from astroid.builder import parse
diff --git a/astroid/brain/brain_http.py b/astroid/brain/brain_http.py
index b8d0f36..acf07bd 100644
--- a/astroid/brain/brain_http.py
+++ b/astroid/brain/brain_http.py
@@ -1,10 +1,6 @@
-# Copyright (c) 2019-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid brain hints for some of the `http` module."""
 import textwrap
diff --git a/astroid/brain/brain_hypothesis.py b/astroid/brain/brain_hypothesis.py
index 06a01dd..dae8361 100644
--- a/astroid/brain/brain_hypothesis.py
+++ b/astroid/brain/brain_hypothesis.py
@@ -1,5 +1,7 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """
 Astroid hook for the Hypothesis library.
 
diff --git a/astroid/brain/brain_io.py b/astroid/brain/brain_io.py
index aba68da..9957ce9 100644
--- a/astroid/brain/brain_io.py
+++ b/astroid/brain/brain_io.py
@@ -1,10 +1,6 @@
-# Copyright (c) 2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid brain hints for some of the _io C objects."""
 from astroid.manager import AstroidManager
diff --git a/astroid/brain/brain_mechanize.py b/astroid/brain/brain_mechanize.py
index c2bda2d..4c86fd9 100644
--- a/astroid/brain/brain_mechanize.py
+++ b/astroid/brain/brain_mechanize.py
@@ -1,14 +1,6 @@
-# Copyright (c) 2012-2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Peter Kolbus <peter.kolbus@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 from astroid.brain.helpers import register_module_extender
 from astroid.builder import AstroidBuilder
diff --git a/astroid/brain/brain_multiprocessing.py b/astroid/brain/brain_multiprocessing.py
index ca663d4..fc98a06 100644
--- a/astroid/brain/brain_multiprocessing.py
+++ b/astroid/brain/brain_multiprocessing.py
@@ -1,12 +1,6 @@
-# Copyright (c) 2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 from astroid.bases import BoundMethod
 from astroid.brain.helpers import register_module_extender
diff --git a/astroid/brain/brain_namedtuple_enum.py b/astroid/brain/brain_namedtuple_enum.py
index 1ca661f..dbd9667 100644
--- a/astroid/brain/brain_namedtuple_enum.py
+++ b/astroid/brain/brain_namedtuple_enum.py
@@ -1,39 +1,18 @@
-# Copyright (c) 2012-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru>
-# Copyright (c) 2015 David Shea <dshea@redhat.com>
-# Copyright (c) 2015 Philip Lorenz <philip@bithub.de>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2016 Mateusz Bysiek <mb@mbdev.pl>
-# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Ram Rachum <ram@rachum.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Dimitri Prybysh <dmand@yandex.ru>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for the Python standard library."""
 
 import functools
 import keyword
 from textwrap import dedent
+from typing import Iterator, List, Optional, Tuple
 
 import astroid
 from astroid import arguments, inference_tip, nodes, util
 from astroid.builder import AstroidBuilder, extract_node
+from astroid.context import InferenceContext
 from astroid.exceptions import (
     AstroidTypeError,
     AstroidValueError,
@@ -89,7 +68,12 @@ def _find_func_form_arguments(node, context):
     raise UseInferenceDefault()
 
 
-def infer_func_form(node, base_type, context=None, enum=False):
+def infer_func_form(
+    node: nodes.Call,
+    base_type: nodes.NodeNG,
+    context: Optional[InferenceContext] = None,
+    enum: bool = False,
+) -> Tuple[nodes.ClassDef, str, List[str]]:
     """Specific inference function for namedtuple or Python 3 enum."""
     # node is a Call node, class name as first argument and generated class
     # attributes as second argument
@@ -101,10 +85,13 @@ def infer_func_form(node, base_type, context=None, enum=False):
         try:
             attributes = names.value.replace(",", " ").split()
         except AttributeError as exc:
+            # Handle attributes of NamedTuples
             if not enum:
                 attributes = [
                     _infer_first(const, context).value for const in names.elts
                 ]
+
+            # Handle attributes of Enums
             else:
                 # Enums supports either iterator of (name, value) pairs
                 # or mappings.
@@ -146,10 +133,17 @@ def infer_func_form(node, base_type, context=None, enum=False):
     # we know it is a namedtuple anyway.
     name = name or "Uninferable"
     # we want to return a Class node instance with proper attributes set
-    class_node = nodes.ClassDef(name, "docstring")
+    class_node = nodes.ClassDef(name)
+    # A typical ClassDef automatically adds its name to the parent scope,
+    # but doing so causes problems, so defer setting parent until after init
+    # see: https://github.com/PyCQA/pylint/issues/5982
     class_node.parent = node.parent
-    # set base class=tuple
-    class_node.bases.append(base_type)
+    class_node.postinit(
+        # set base class=tuple
+        bases=[base_type],
+        body=[],
+        decorators=None,
+    )
     # XXX add __init__(*attributes) method
     for attr in attributes:
         fake_node = nodes.EmptyNode()
@@ -182,7 +176,9 @@ _looks_like_enum = functools.partial(_looks_like, name="Enum")
 _looks_like_typing_namedtuple = functools.partial(_looks_like, name="NamedTuple")
 
 
-def infer_named_tuple(node, context=None):
+def infer_named_tuple(
+    node: nodes.Call, context: Optional[InferenceContext] = None
+) -> Iterator[nodes.ClassDef]:
     """Specific inference function for namedtuple Call node"""
     tuple_base_name = nodes.Name(name="tuple", parent=node.root())
     class_node, name, attributes = infer_func_form(
@@ -506,7 +502,9 @@ def infer_typing_namedtuple_function(node, context=None):
     return klass.infer(context)
 
 
-def infer_typing_namedtuple(node, context=None):
+def infer_typing_namedtuple(
+    node: nodes.Call, context: Optional[InferenceContext] = None
+) -> Iterator[nodes.ClassDef]:
     """Infer a typing.NamedTuple(...) call."""
     # This is essentially a namedtuple with different arguments
     # so we extract the args and infer a named tuple.
diff --git a/astroid/brain/brain_nose.py b/astroid/brain/brain_nose.py
index f4a0525..38e2229 100644
--- a/astroid/brain/brain_nose.py
+++ b/astroid/brain/brain_nose.py
@@ -1,12 +1,6 @@
-# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Hooks for nose library."""
 
diff --git a/astroid/brain/brain_numpy_core_fromnumeric.py b/astroid/brain/brain_numpy_core_fromnumeric.py
index ea9fae2..19d4822 100644
--- a/astroid/brain/brain_numpy_core_fromnumeric.py
+++ b/astroid/brain/brain_numpy_core_fromnumeric.py
@@ -1,11 +1,6 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for numpy.core.fromnumeric module."""
 from astroid.brain.helpers import register_module_extender
diff --git a/astroid/brain/brain_numpy_core_function_base.py b/astroid/brain/brain_numpy_core_function_base.py
index 95a65cb..31d53cb 100644
--- a/astroid/brain/brain_numpy_core_function_base.py
+++ b/astroid/brain/brain_numpy_core_function_base.py
@@ -1,11 +1,6 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for numpy.core.function_base module."""
 
diff --git a/astroid/brain/brain_numpy_core_multiarray.py b/astroid/brain/brain_numpy_core_multiarray.py
index 0a97724..487ec47 100644
--- a/astroid/brain/brain_numpy_core_multiarray.py
+++ b/astroid/brain/brain_numpy_core_multiarray.py
@@ -1,11 +1,6 @@
-# Copyright (c) 2019-2020 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for numpy.core.multiarray module."""
 
diff --git a/astroid/brain/brain_numpy_core_numeric.py b/astroid/brain/brain_numpy_core_numeric.py
index 56c7ede..140d81a 100644
--- a/astroid/brain/brain_numpy_core_numeric.py
+++ b/astroid/brain/brain_numpy_core_numeric.py
@@ -1,11 +1,6 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for numpy.core.numeric module."""
 
@@ -24,9 +19,9 @@ def numpy_core_numeric_transform():
         """
     # different functions defined in numeric.py
     import numpy
-    def zeros_like(a, dtype=None, order='K', subok=True): return numpy.ndarray((0, 0))
-    def ones_like(a, dtype=None, order='K', subok=True): return numpy.ndarray((0, 0))
-    def full_like(a, fill_value, dtype=None, order='K', subok=True): return numpy.ndarray((0, 0))
+    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))
         """
     )
 
diff --git a/astroid/brain/brain_numpy_core_numerictypes.py b/astroid/brain/brain_numpy_core_numerictypes.py
index 6ad1305..245296e 100644
--- a/astroid/brain/brain_numpy_core_numerictypes.py
+++ b/astroid/brain/brain_numpy_core_numerictypes.py
@@ -1,10 +1,6 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 # TODO(hippo91) : correct the methods signature.
 
diff --git a/astroid/brain/brain_numpy_core_umath.py b/astroid/brain/brain_numpy_core_umath.py
index 3b1bcb8..42dfdfa 100644
--- a/astroid/brain/brain_numpy_core_umath.py
+++ b/astroid/brain/brain_numpy_core_umath.py
@@ -1,10 +1,6 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 # Note: starting with version 1.18 numpy module has `__getattr__` method which prevent
 # `pylint` to emit `no-member` message for all numpy's attributes. (see pylint's module
diff --git a/astroid/brain/brain_numpy_ma.py b/astroid/brain/brain_numpy_ma.py
index 8ae9465..241665c 100644
--- a/astroid/brain/brain_numpy_ma.py
+++ b/astroid/brain/brain_numpy_ma.py
@@ -1,7 +1,7 @@
-# Copyright (c) 2021 hippo91 <guillaume.peillex@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """Astroid hooks for numpy ma module"""
 
 from astroid.brain.helpers import register_module_extender
diff --git a/astroid/brain/brain_numpy_ndarray.py b/astroid/brain/brain_numpy_ndarray.py
index 6578354..f9b611e 100644
--- a/astroid/brain/brain_numpy_ndarray.py
+++ b/astroid/brain/brain_numpy_ndarray.py
@@ -1,12 +1,6 @@
-# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2017-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for numpy ndarray class."""
 from astroid.brain.brain_numpy_utils import numpy_supports_type_hints
diff --git a/astroid/brain/brain_numpy_random_mtrand.py b/astroid/brain/brain_numpy_random_mtrand.py
index ddb1f03..b1f0d45 100644
--- a/astroid/brain/brain_numpy_random_mtrand.py
+++ b/astroid/brain/brain_numpy_random_mtrand.py
@@ -1,10 +1,6 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 # TODO(hippo91) : correct the functions return types
 """Astroid hooks for numpy.random.mtrand module."""
diff --git a/astroid/brain/brain_numpy_utils.py b/astroid/brain/brain_numpy_utils.py
index 3686a7a..c32d6d6 100644
--- a/astroid/brain/brain_numpy_utils.py
+++ b/astroid/brain/brain_numpy_utils.py
@@ -1,12 +1,6 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2019-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Different utilities for the numpy brains"""
 from typing import Tuple
diff --git a/astroid/brain/brain_pkg_resources.py b/astroid/brain/brain_pkg_resources.py
index d45e898..689dd74 100644
--- a/astroid/brain/brain_pkg_resources.py
+++ b/astroid/brain/brain_pkg_resources.py
@@ -1,11 +1,6 @@
-# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 from astroid import parse
 from astroid.brain.helpers import register_module_extender
diff --git a/astroid/brain/brain_pytest.py b/astroid/brain/brain_pytest.py
index fa61313..78c9779 100644
--- a/astroid/brain/brain_pytest.py
+++ b/astroid/brain/brain_pytest.py
@@ -1,14 +1,6 @@
-# Copyright (c) 2014-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Jeff Quast <contact@jeffquast.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2016 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for pytest."""
 from astroid.brain.helpers import register_module_extender
diff --git a/astroid/brain/brain_qt.py b/astroid/brain/brain_qt.py
index 5d564c5..c025084 100644
--- a/astroid/brain/brain_qt.py
+++ b/astroid/brain/brain_qt.py
@@ -1,14 +1,6 @@
-# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2017 Roy Wright <roy@wright.org>
-# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2019 Antoine Boellinger <aboellinger@hotmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for the PyQT library."""
 
@@ -29,7 +21,7 @@ def _looks_like_signal(node, signal_name="pyqtSignal"):
     return False
 
 
-def transform_pyqt_signal(node):
+def transform_pyqt_signal(node: nodes.FunctionDef) -> None:
     module = parse(
         """
     class pyqtSignal(object):
@@ -41,13 +33,13 @@ def transform_pyqt_signal(node):
             pass
     """
     )
-    signal_cls = module["pyqtSignal"]
-    node.instance_attrs["emit"] = signal_cls["emit"]
-    node.instance_attrs["disconnect"] = signal_cls["disconnect"]
-    node.instance_attrs["connect"] = signal_cls["connect"]
+    signal_cls: nodes.ClassDef = module["pyqtSignal"]
+    node.instance_attrs["emit"] = [signal_cls["emit"]]
+    node.instance_attrs["disconnect"] = [signal_cls["disconnect"]]
+    node.instance_attrs["connect"] = [signal_cls["connect"]]
 
 
-def transform_pyside_signal(node):
+def transform_pyside_signal(node: nodes.FunctionDef) -> None:
     module = parse(
         """
     class NotPySideSignal(object):
@@ -59,10 +51,10 @@ def transform_pyside_signal(node):
             pass
     """
     )
-    signal_cls = module["NotPySideSignal"]
-    node.instance_attrs["connect"] = signal_cls["connect"]
-    node.instance_attrs["disconnect"] = signal_cls["disconnect"]
-    node.instance_attrs["emit"] = signal_cls["emit"]
+    signal_cls: nodes.ClassDef = module["NotPySideSignal"]
+    node.instance_attrs["connect"] = [signal_cls["connect"]]
+    node.instance_attrs["disconnect"] = [signal_cls["disconnect"]]
+    node.instance_attrs["emit"] = [signal_cls["emit"]]
 
 
 def pyqt4_qtcore_transform():
diff --git a/astroid/brain/brain_random.py b/astroid/brain/brain_random.py
index 7b99c21..e66aa81 100644
--- a/astroid/brain/brain_random.py
+++ b/astroid/brain/brain_random.py
@@ -1,5 +1,7 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import random
 
 from astroid import helpers
diff --git a/astroid/brain/brain_re.py b/astroid/brain/brain_re.py
index ecd4235..0dd346a 100644
--- a/astroid/brain/brain_re.py
+++ b/astroid/brain/brain_re.py
@@ -1,10 +1,12 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 from typing import Optional
 
 from astroid import context, inference_tip, nodes
 from astroid.brain.helpers import register_module_extender
-from astroid.builder import extract_node, parse
+from astroid.builder import _extract_single_node, parse
 from astroid.const import PY37_PLUS, PY39_PLUS
 from astroid.manager import AstroidManager
 
@@ -77,7 +79,7 @@ def infer_pattern_match(
         parent=node.parent,
     )
     if PY39_PLUS:
-        func_to_add = extract_node(CLASS_GETITEM_TEMPLATE)
+        func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE)
         class_def.locals["__class_getitem__"] = [func_to_add]
     return iter([class_def])
 
diff --git a/astroid/brain/brain_responses.py b/astroid/brain/brain_responses.py
index d034121..0fb0e42 100644
--- a/astroid/brain/brain_responses.py
+++ b/astroid/brain/brain_responses.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """
 Astroid hooks for responses.
 
diff --git a/astroid/brain/brain_scipy_signal.py b/astroid/brain/brain_scipy_signal.py
index 856856a..578022f 100755
--- a/astroid/brain/brain_scipy_signal.py
+++ b/astroid/brain/brain_scipy_signal.py
@@ -1,94 +1,88 @@
-# Copyright (c) 2019 Valentin Valls <valentin.valls@esrf.fr>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
-# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
-# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
-
-"""Astroid hooks for scipy.signal module."""
-from astroid.brain.helpers import register_module_extender
-from astroid.builder import parse
-from astroid.manager import AstroidManager
-
-
-def scipy_signal():
-    return parse(
-        """
-    # different functions defined in scipy.signals
-
-    def barthann(M, sym=True):
-        return numpy.ndarray([0])
-
-    def bartlett(M, sym=True):
-        return numpy.ndarray([0])
-
-    def blackman(M, sym=True):
-        return numpy.ndarray([0])
-
-    def blackmanharris(M, sym=True):
-        return numpy.ndarray([0])
-
-    def bohman(M, sym=True):
-        return numpy.ndarray([0])
-
-    def boxcar(M, sym=True):
-        return numpy.ndarray([0])
-
-    def chebwin(M, at, sym=True):
-        return numpy.ndarray([0])
-
-    def cosine(M, sym=True):
-        return numpy.ndarray([0])
-
-    def exponential(M, center=None, tau=1.0, sym=True):
-        return numpy.ndarray([0])
-
-    def flattop(M, sym=True):
-        return numpy.ndarray([0])
-
-    def gaussian(M, std, sym=True):
-        return numpy.ndarray([0])
-
-    def general_gaussian(M, p, sig, sym=True):
-        return numpy.ndarray([0])
-
-    def hamming(M, sym=True):
-        return numpy.ndarray([0])
-
-    def hann(M, sym=True):
-        return numpy.ndarray([0])
-
-    def hanning(M, sym=True):
-        return numpy.ndarray([0])
-
-    def impulse2(system, X0=None, T=None, N=None, **kwargs):
-        return numpy.ndarray([0]), numpy.ndarray([0])
-
-    def kaiser(M, beta, sym=True):
-        return numpy.ndarray([0])
-
-    def nuttall(M, sym=True):
-        return numpy.ndarray([0])
-
-    def parzen(M, sym=True):
-        return numpy.ndarray([0])
-
-    def slepian(M, width, sym=True):
-        return numpy.ndarray([0])
-
-    def step2(system, X0=None, T=None, N=None, **kwargs):
-        return numpy.ndarray([0]), numpy.ndarray([0])
-
-    def triang(M, sym=True):
-        return numpy.ndarray([0])
-
-    def tukey(M, alpha=0.5, sym=True):
-        return numpy.ndarray([0])
-        """
-    )
-
-
-register_module_extender(AstroidManager(), "scipy.signal", scipy_signal)
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
+"""Astroid hooks for scipy.signal module."""
+from astroid.brain.helpers import register_module_extender
+from astroid.builder import parse
+from astroid.manager import AstroidManager
+
+
+def scipy_signal():
+    return parse(
+        """
+    # different functions defined in scipy.signals
+
+    def barthann(M, sym=True):
+        return numpy.ndarray([0])
+
+    def bartlett(M, sym=True):
+        return numpy.ndarray([0])
+
+    def blackman(M, sym=True):
+        return numpy.ndarray([0])
+
+    def blackmanharris(M, sym=True):
+        return numpy.ndarray([0])
+
+    def bohman(M, sym=True):
+        return numpy.ndarray([0])
+
+    def boxcar(M, sym=True):
+        return numpy.ndarray([0])
+
+    def chebwin(M, at, sym=True):
+        return numpy.ndarray([0])
+
+    def cosine(M, sym=True):
+        return numpy.ndarray([0])
+
+    def exponential(M, center=None, tau=1.0, sym=True):
+        return numpy.ndarray([0])
+
+    def flattop(M, sym=True):
+        return numpy.ndarray([0])
+
+    def gaussian(M, std, sym=True):
+        return numpy.ndarray([0])
+
+    def general_gaussian(M, p, sig, sym=True):
+        return numpy.ndarray([0])
+
+    def hamming(M, sym=True):
+        return numpy.ndarray([0])
+
+    def hann(M, sym=True):
+        return numpy.ndarray([0])
+
+    def hanning(M, sym=True):
+        return numpy.ndarray([0])
+
+    def impulse2(system, X0=None, T=None, N=None, **kwargs):
+        return numpy.ndarray([0]), numpy.ndarray([0])
+
+    def kaiser(M, beta, sym=True):
+        return numpy.ndarray([0])
+
+    def nuttall(M, sym=True):
+        return numpy.ndarray([0])
+
+    def parzen(M, sym=True):
+        return numpy.ndarray([0])
+
+    def slepian(M, width, sym=True):
+        return numpy.ndarray([0])
+
+    def step2(system, X0=None, T=None, N=None, **kwargs):
+        return numpy.ndarray([0]), numpy.ndarray([0])
+
+    def triang(M, sym=True):
+        return numpy.ndarray([0])
+
+    def tukey(M, alpha=0.5, sym=True):
+        return numpy.ndarray([0])
+        """
+    )
+
+
+register_module_extender(AstroidManager(), "scipy.signal", scipy_signal)
diff --git a/astroid/brain/brain_signal.py b/astroid/brain/brain_signal.py
index 46a6413..5eee7f6 100644
--- a/astroid/brain/brain_signal.py
+++ b/astroid/brain/brain_signal.py
@@ -1,5 +1,7 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """Astroid hooks for the signal library.
 
 The signal module generates the 'Signals', 'Handlers' and 'Sigmasks' IntEnums
diff --git a/astroid/brain/brain_six.py b/astroid/brain/brain_six.py
index 074c5e8..022fcf2 100644
--- a/astroid/brain/brain_six.py
+++ b/astroid/brain/brain_six.py
@@ -1,16 +1,6 @@
-# Copyright (c) 2014-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Ram Rachum <ram@rachum.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Artsiom Kaval <lezeroq@gmail.com>
-# Copyright (c) 2021 Francis Charette Migneault <francis.charette.migneault@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for six module."""
 
diff --git a/astroid/brain/brain_sqlalchemy.py b/astroid/brain/brain_sqlalchemy.py
index d2352ce..f3695de 100644
--- a/astroid/brain/brain_sqlalchemy.py
+++ b/astroid/brain/brain_sqlalchemy.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 from astroid.brain.helpers import register_module_extender
 from astroid.builder import parse
 from astroid.manager import AstroidManager
diff --git a/astroid/brain/brain_ssl.py b/astroid/brain/brain_ssl.py
index 8c2284e..6ca0d5a 100644
--- a/astroid/brain/brain_ssl.py
+++ b/astroid/brain/brain_ssl.py
@@ -1,12 +1,6 @@
-# Copyright (c) 2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2019 Benjamin Elven <25181435+S3ntinelX@users.noreply.github.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for the ssl library."""
 
diff --git a/astroid/brain/brain_subprocess.py b/astroid/brain/brain_subprocess.py
index b9d4f88..ec52e0b 100644
--- a/astroid/brain/brain_subprocess.py
+++ b/astroid/brain/brain_subprocess.py
@@ -1,17 +1,6 @@
-# Copyright (c) 2016-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2018 Peter Talley <peterctalley@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Peter Pentchev <roam@ringlet.net>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Damien Baty <damien@damienbaty.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import textwrap
 
diff --git a/astroid/brain/brain_threading.py b/astroid/brain/brain_threading.py
index f872530..a85055d 100644
--- a/astroid/brain/brain_threading.py
+++ b/astroid/brain/brain_threading.py
@@ -1,11 +1,6 @@
-# Copyright (c) 2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 from astroid.brain.helpers import register_module_extender
 from astroid.builder import parse
diff --git a/astroid/brain/brain_type.py b/astroid/brain/brain_type.py
index 9d694e6..f9c3ff4 100644
--- a/astroid/brain/brain_type.py
+++ b/astroid/brain/brain_type.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """
 Astroid hooks for type support.
 
diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py
index d5fab0b..6077773 100644
--- a/astroid/brain/brain_typing.py
+++ b/astroid/brain/brain_typing.py
@@ -1,22 +1,13 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
-# Copyright (c) 2017-2018 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 David Euresti <github@euresti.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Redoubts <Redoubts@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Tim Martin <tim@asymptotic.co.uk>
-# Copyright (c) 2021 hippo91 <guillaume.peillex@gmail.com>
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for typing.py support."""
 import typing
 from functools import partial
 
 from astroid import context, extract_node, inference_tip
+from astroid.builder import _extract_single_node
 from astroid.const import PY37_PLUS, PY38_PLUS, PY39_PLUS
 from astroid.exceptions import (
     AttributeInferenceError,
@@ -30,6 +21,7 @@ from astroid.nodes.node_classes import (
     Attribute,
     Call,
     Const,
+    JoinedStr,
     Name,
     NodeNG,
     Subscript,
@@ -127,6 +119,9 @@ def infer_typing_typevar_or_newtype(node, context_itton=None):
         raise UseInferenceDefault
     if not node.args:
         raise UseInferenceDefault
+    # Cannot infer from a dynamic class name (f-string)
+    if isinstance(node.args[0], JoinedStr):
+        raise UseInferenceDefault
 
     typename = node.args[0].as_string().strip("'")
     node = extract_node(TYPING_TYPE_TEMPLATE.format(typename))
@@ -149,7 +144,7 @@ def infer_typing_attr(
 ) -> typing.Iterator[ClassDef]:
     """Infer a typing.X[...] subscript"""
     try:
-        value = next(node.value.infer())
+        value = next(node.value.infer())  # type: ignore[union-attr] # value shouldn't be None for Subscript.
     except (InferenceError, StopIteration) as exc:
         raise UseInferenceDefault from exc
 
@@ -171,7 +166,7 @@ def infer_typing_attr(
         # With PY37+ typing.Generic and typing.Annotated (PY39) are subscriptable
         # through __class_getitem__. Since astroid can't easily
         # infer the native methods, replace them for an easy inference tip
-        func_to_add = extract_node(CLASS_GETITEM_TEMPLATE)
+        func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE)
         value.locals["__class_getitem__"] = [func_to_add]
         if (
             isinstance(node.parent, ClassDef)
@@ -199,7 +194,7 @@ def _looks_like_typedDict(  # pylint: disable=invalid-name
 def infer_old_typedDict(  # pylint: disable=invalid-name
     node: ClassDef, ctx: typing.Optional[context.InferenceContext] = None
 ) -> typing.Iterator[ClassDef]:
-    func_to_add = extract_node("dict")
+    func_to_add = _extract_single_node("dict")
     node.locals["__call__"] = [func_to_add]
     return iter([node])
 
@@ -215,7 +210,7 @@ def infer_typedDict(  # pylint: disable=invalid-name
         parent=node.parent,
     )
     class_def.postinit(bases=[extract_node("dict")], body=[], decorators=None)
-    func_to_add = extract_node("dict")
+    func_to_add = _extract_single_node("dict")
     class_def.locals["__call__"] = [func_to_add]
     return iter([class_def])
 
@@ -308,7 +303,7 @@ def infer_typing_alias(
         and maybe_type_var.value > 0
     ):
         # If typing alias is subscriptable, add `__class_getitem__` to ClassDef
-        func_to_add = extract_node(CLASS_GETITEM_TEMPLATE)
+        func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE)
         class_def.locals["__class_getitem__"] = [func_to_add]
     else:
         # If not, make sure that `__class_getitem__` access is forbidden.
@@ -373,7 +368,7 @@ def infer_special_alias(
         parent=node.parent,
     )
     class_def.postinit(bases=[res], body=[], decorators=None)
-    func_to_add = extract_node(CLASS_GETITEM_TEMPLATE)
+    func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE)
     class_def.locals["__class_getitem__"] = [func_to_add]
     return iter([class_def])
 
diff --git a/astroid/brain/brain_unittest.py b/astroid/brain/brain_unittest.py
index d371d38..b34e1cf 100644
--- a/astroid/brain/brain_unittest.py
+++ b/astroid/brain/brain_unittest.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """Astroid hooks for unittest module"""
 from astroid.brain.helpers import register_module_extender
 from astroid.builder import parse
diff --git a/astroid/brain/brain_uuid.py b/astroid/brain/brain_uuid.py
index 18ae4a0..f6ba888 100644
--- a/astroid/brain/brain_uuid.py
+++ b/astroid/brain/brain_uuid.py
@@ -1,10 +1,6 @@
-# Copyright (c) 2017-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Astroid hooks for the UUID module."""
 from astroid.manager import AstroidManager
diff --git a/astroid/brain/helpers.py b/astroid/brain/helpers.py
index 0990715..d74f595 100644
--- a/astroid/brain/helpers.py
+++ b/astroid/brain/helpers.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 from astroid.nodes.scoped_nodes import Module
 
 
diff --git a/astroid/builder.py b/astroid/builder.py
index 4d68e4f..1cdb963 100644
--- a/astroid/builder.py
+++ b/astroid/builder.py
@@ -1,23 +1,6 @@
-# Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2013 Phil Schaf <flying-sheep@web.de>
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014-2015 Google, Inc.
-# Copyright (c) 2014 Alexander Presnyakov <flagist0@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Gregory P. Smith <greg@krypto.org>
-# Copyright (c) 2021 Kian Meng, Ang <kianmeng.ang@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """The AstroidBuilder makes astroid from living object and / or from _ast
 
@@ -28,7 +11,7 @@ import os
 import textwrap
 import types
 from tokenize import detect_encoding
-from typing import List, Optional, Union
+from typing import List, Optional, Tuple, Union
 
 from astroid import bases, modutils, nodes, raw_building, rebuilder, util
 from astroid._ast import get_parser_module
@@ -147,27 +130,29 @@ class AstroidBuilder(raw_building.InspectBuilder):
                 except ImportError:
                     modname = os.path.splitext(os.path.basename(path))[0]
             # build astroid representation
-            module = self._data_build(data, modname, path)
-            return self._post_build(module, encoding)
+            module, builder = self._data_build(data, modname, path)
+            return self._post_build(module, builder, encoding)
 
     def string_build(self, data, modname="", path=None):
         """Build astroid from source code string."""
-        module = self._data_build(data, modname, path)
+        module, builder = self._data_build(data, modname, path)
         module.file_bytes = data.encode("utf-8")
-        return self._post_build(module, "utf-8")
+        return self._post_build(module, builder, "utf-8")
 
-    def _post_build(self, module, encoding):
+    def _post_build(
+        self, module: nodes.Module, builder: rebuilder.TreeRebuilder, encoding: str
+    ) -> nodes.Module:
         """Handles encoding and delayed nodes after a module has been built"""
         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 module._import_from_nodes:
+        for from_node 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)
         # handle delayed assattr nodes
-        for delayed in module._delayed_assattr:
+        for delayed in builder._delayed_assattr:
             self.delayed_assattr(delayed)
 
         # Visit the transforms
@@ -175,8 +160,10 @@ class AstroidBuilder(raw_building.InspectBuilder):
             module = self._manager.visit_transforms(module)
         return module
 
-    def _data_build(self, data, modname, path):
-        """Build tree node from data and add some information"""
+    def _data_build(
+        self, data: str, modname, path
+    ) -> Tuple[nodes.Module, rebuilder.TreeRebuilder]:
+        """Build tree node from data and add some informations"""
         try:
             node, parser_module = _parse_string(data, type_comments=True)
         except (TypeError, ValueError, SyntaxError) as exc:
@@ -200,11 +187,9 @@ class AstroidBuilder(raw_building.InspectBuilder):
                 path is not None
                 and os.path.splitext(os.path.basename(path))[0] == "__init__"
             )
-        builder = rebuilder.TreeRebuilder(self._manager, parser_module)
+        builder = rebuilder.TreeRebuilder(self._manager, parser_module, data)
         module = builder.visit_module(node, modname, node_file, package)
-        module._import_from_nodes = builder._import_from_nodes
-        module._delayed_assattr = builder._delayed_assattr
-        return module
+        return module, builder
 
     def add_from_names_to_locals(self, node):
         """Store imported names to the locals
@@ -276,7 +261,7 @@ class AstroidBuilder(raw_building.InspectBuilder):
 
 
 def build_namespace_package_module(name: str, path: List[str]) -> nodes.Module:
-    return nodes.Module(name, doc="", path=path, package=True)
+    return nodes.Module(name, path=path, package=True)
 
 
 def parse(code, module_name="", path=None, apply_transforms=True):
@@ -455,6 +440,14 @@ def extract_node(code: str, module_name: str = "") -> Union[NodeNG, List[NodeNG]
     return extracted
 
 
+def _extract_single_node(code: str, module_name: str = "") -> NodeNG:
+    """Call extract_node while making sure that only one value is returned."""
+    ret = extract_node(code, module_name)
+    if isinstance(ret, list):
+        return ret[0]
+    return ret
+
+
 def _parse_string(data, type_comments=True):
     parser_module = get_parser_module(type_comments=type_comments)
     try:
diff --git a/astroid/const.py b/astroid/const.py
index a1bc4bf..a7fbe06 100644
--- a/astroid/const.py
+++ b/astroid/const.py
@@ -1,6 +1,11 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import enum
 import sys
 
+PY36 = sys.version_info[:2] == (3, 6)
 PY38 = sys.version_info[:2] == (3, 8)
 PY37_PLUS = sys.version_info >= (3, 7)
 PY38_PLUS = sys.version_info >= (3, 8)
@@ -8,6 +13,11 @@ PY39_PLUS = sys.version_info >= (3, 9)
 PY310_PLUS = sys.version_info >= (3, 10)
 BUILTINS = "builtins"  # TODO Remove in 2.8
 
+WIN32 = sys.platform == "win32"
+
+IS_PYPY = sys.implementation.name == "pypy"
+IS_JYTHON = sys.implementation.name == "jython"
+
 
 class Context(enum.Enum):
     Load = 1
diff --git a/astroid/context.py b/astroid/context.py
index 4125fde..a04996e 100644
--- a/astroid/context.py
+++ b/astroid/context.py
@@ -1,18 +1,6 @@
-# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Bryce Guinta <bryce.guinta@protonmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Kian Meng, Ang <kianmeng.ang@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Various context related utilities, including inference and call contexts."""
 import contextlib
diff --git a/astroid/decorators.py b/astroid/decorators.py
index 96d3bba..ec16fea 100644
--- a/astroid/decorators.py
+++ b/astroid/decorators.py
@@ -1,20 +1,6 @@
-# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2018, 2021 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018 Tomas Gavenciak <gavento@ucw.cz>
-# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Ram Rachum <ram@rachum.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """ A few useful function/method decorators."""
 
@@ -52,6 +38,8 @@ def cached(func, instance, args, kwargs):
         return result
 
 
+# TODO: Remove when support for 3.7 is dropped
+# TODO: astroid 3.0 -> move class behind sys.version_info < (3, 8) guard
 class cachedproperty:
     """Provides a cached property equivalent to the stacking of
     @cached and @property, but more efficient.
@@ -70,6 +58,12 @@ class cachedproperty:
     __slots__ = ("wrapped",)
 
     def __init__(self, wrapped):
+        if sys.version_info >= (3, 8):
+            warnings.warn(
+                "cachedproperty has been deprecated and will be removed in astroid 3.0 for Python 3.8+. "
+                "Use functools.cached_property instead.",
+                DeprecationWarning,
+            )
         try:
             wrapped.__name__
         except AttributeError as exc:
@@ -152,58 +146,128 @@ def raise_if_nothing_inferred(func, instance, args, kwargs):
     yield from generator
 
 
-def deprecate_default_argument_values(
-    astroid_version: str = "3.0", **arguments: str
-) -> Callable[[Callable[P, R]], Callable[P, R]]:
-    """Decorator which emitts a DeprecationWarning if any arguments specified
-    are None or not passed at all.
+# Expensive decorators only used to emit Deprecation warnings.
+# If no other than the default DeprecationWarning are enabled,
+# fall back to passthrough implementations.
+if util.check_warnings_filter():
+
+    def deprecate_default_argument_values(
+        astroid_version: str = "3.0", **arguments: str
+    ) -> Callable[[Callable[P, R]], Callable[P, R]]:
+        """Decorator which emits a DeprecationWarning if any arguments specified
+        are None or not passed at all.
+
+        Arguments should be a key-value mapping, with the key being the argument to check
+        and the value being a type annotation as string for the value of the argument.
+
+        To improve performance, only used when DeprecationWarnings other than
+        the default one are enabled.
+        """
+        # Helpful links
+        # Decorator for DeprecationWarning: https://stackoverflow.com/a/49802489
+        # Typing of stacked decorators: https://stackoverflow.com/a/68290080
+
+        def deco(func: Callable[P, R]) -> Callable[P, R]:
+            """Decorator function."""
+
+            @functools.wraps(func)
+            def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
+                """Emit DeprecationWarnings if conditions are met."""
+
+                keys = list(inspect.signature(func).parameters.keys())
+                for arg, type_annotation in arguments.items():
+                    try:
+                        index = keys.index(arg)
+                    except ValueError:
+                        raise Exception(
+                            f"Can't find argument '{arg}' for '{args[0].__class__.__qualname__}'"
+                        ) from None
+                    if (
+                        # Check kwargs
+                        # - if found, check it's not None
+                        (arg in kwargs and kwargs[arg] is None)
+                        # Check args
+                        # - make sure not in kwargs
+                        # - len(args) needs to be long enough, if too short
+                        #   arg can't be in args either
+                        # - args[index] should not be None
+                        or arg not in kwargs
+                        and (
+                            index == -1
+                            or len(args) <= index
+                            or (len(args) > index and args[index] is None)
+                        )
+                    ):
+                        warnings.warn(
+                            f"'{arg}' will be a required argument for "
+                            f"'{args[0].__class__.__qualname__}.{func.__name__}' in astroid {astroid_version} "
+                            f"('{arg}' should be of type: '{type_annotation}')",
+                            DeprecationWarning,
+                        )
+                return func(*args, **kwargs)
+
+            return wrapper
+
+        return deco
+
+    def deprecate_arguments(
+        astroid_version: str = "3.0", **arguments: str
+    ) -> Callable[[Callable[P, R]], Callable[P, R]]:
+        """Decorator which emits a DeprecationWarning if any arguments specified
+        are passed.
+
+        Arguments should be a key-value mapping, with the key being the argument to check
+        and the value being a string that explains what to do instead of passing the argument.
+
+        To improve performance, only used when DeprecationWarnings other than
+        the default one are enabled.
+        """
+
+        def deco(func: Callable[P, R]) -> Callable[P, R]:
+            @functools.wraps(func)
+            def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
+
+                keys = list(inspect.signature(func).parameters.keys())
+                for arg, note in arguments.items():
+                    try:
+                        index = keys.index(arg)
+                    except ValueError:
+                        raise Exception(
+                            f"Can't find argument '{arg}' for '{args[0].__class__.__qualname__}'"
+                        ) from None
+                    if arg in kwargs or len(args) > index:
+                        warnings.warn(
+                            f"The argument '{arg}' for "
+                            f"'{args[0].__class__.__qualname__}.{func.__name__}' is deprecated "
+                            f"and will be removed in astroid {astroid_version} ({note})",
+                            DeprecationWarning,
+                        )
+                return func(*args, **kwargs)
+
+            return wrapper
+
+        return deco
 
-    Arguments should be a key-value mapping, with the key being the argument to check
-    and the value being a type annotation as string for the value of the argument.
-    """
-    # Helpful links
-    # Decorator for DeprecationWarning: https://stackoverflow.com/a/49802489
-    # Typing of stacked decorators: https://stackoverflow.com/a/68290080
-
-    def deco(func: Callable[P, R]) -> Callable[P, R]:
-        """Decorator function."""
-
-        @functools.wraps(func)
-        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
-            """Emit DeprecationWarnings if conditions are met."""
-
-            keys = list(inspect.signature(func).parameters.keys())
-            for arg, type_annotation in arguments.items():
-                try:
-                    index = keys.index(arg)
-                except ValueError:
-                    raise Exception(
-                        f"Can't find argument '{arg}' for '{args[0].__class__.__qualname__}'"
-                    ) from None
-                if (
-                    # Check kwargs
-                    # - if found, check it's not None
-                    (arg in kwargs and kwargs[arg] is None)
-                    # Check args
-                    # - make sure not in kwargs
-                    # - len(args) needs to be long enough, if too short
-                    #   arg can't be in args either
-                    # - args[index] should not be None
-                    or arg not in kwargs
-                    and (
-                        index == -1
-                        or len(args) <= index
-                        or (len(args) > index and args[index] is None)
-                    )
-                ):
-                    warnings.warn(
-                        f"'{arg}' will be a required argument for "
-                        f"'{args[0].__class__.__qualname__}.{func.__name__}' in astroid {astroid_version} "
-                        f"('{arg}' should be of type: '{type_annotation}')",
-                        DeprecationWarning,
-                    )
-            return func(*args, **kwargs)
-
-        return wrapper
-
-    return deco
+else:
+
+    def deprecate_default_argument_values(
+        astroid_version: str = "3.0", **arguments: str
+    ) -> Callable[[Callable[P, R]], Callable[P, R]]:
+        """Passthrough decorator to improve performance if DeprecationWarnings are disabled."""
+
+        def deco(func: Callable[P, R]) -> Callable[P, R]:
+            """Decorator function."""
+            return func
+
+        return deco
+
+    def deprecate_arguments(
+        astroid_version: str = "3.0", **arguments: str
+    ) -> Callable[[Callable[P, R]], Callable[P, R]]:
+        """Passthrough decorator to improve performance if DeprecationWarnings are disabled."""
+
+        def deco(func: Callable[P, R]) -> Callable[P, R]:
+            """Decorator function."""
+            return func
+
+        return deco
diff --git a/astroid/exceptions.py b/astroid/exceptions.py
index b883802..c3909b2 100644
--- a/astroid/exceptions.py
+++ b/astroid/exceptions.py
@@ -1,17 +1,6 @@
-# Copyright (c) 2007, 2009-2010, 2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """this module contains exceptions used in the astroid library
 """
@@ -42,7 +31,9 @@ __all__ = (
     "NoDefault",
     "NotFoundError",
     "OperationError",
+    "ParentMissingError",
     "ResolveError",
+    "StatementMissing",
     "SuperArgumentTypeError",
     "SuperError",
     "TooManyLevelsError",
diff --git a/astroid/filter_statements.py b/astroid/filter_statements.py
index 3060b53..528e0be 100644
--- a/astroid/filter_statements.py
+++ b/astroid/filter_statements.py
@@ -1,5 +1,6 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """_filter_stmts and helper functions. This method gets used in LocalsDictnodes.NodeNG._scope_lookup.
 It is not considered public.
diff --git a/astroid/helpers.py b/astroid/helpers.py
index a9033d5..527ac1f 100644
--- a/astroid/helpers.py
+++ b/astroid/helpers.py
@@ -1,20 +1,6 @@
-# Copyright (c) 2015-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Simon Hewitt <si@sjhewitt.co.uk>
-# Copyright (c) 2020 Bryce Guinta <bryce.guinta@protonmail.com>
-# Copyright (c) 2020 Ram Rachum <ram@rachum.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """
 Various helper utilities.
diff --git a/astroid/inference.py b/astroid/inference.py
index 74f04dd..d220300 100644
--- a/astroid/inference.py
+++ b/astroid/inference.py
@@ -1,30 +1,6 @@
-# Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
-# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2017 Michał Masłowski <m.maslowski@clearcode.cc>
-# Copyright (c) 2017 Calen Pennington <cale@edx.org>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2018-2019 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018 Daniel Martin <daniel.martin@crowdstrike.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
-# Copyright (c) 2020 Leandro T. C. Melo <ltcmelo@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Kian Meng, Ang <kianmeng.ang@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """this module contains a set of functions to handle inference on astroid trees
 """
@@ -33,7 +9,19 @@ import ast
 import functools
 import itertools
 import operator
-from typing import Any, Callable, Dict, Iterable, Optional
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    Dict,
+    Generator,
+    Iterable,
+    Iterator,
+    Optional,
+    Type,
+    TypeVar,
+    Union,
+)
 
 import wrapt
 
@@ -56,11 +44,18 @@ from astroid.exceptions import (
 )
 from astroid.interpreter import dunder_lookup
 from astroid.manager import AstroidManager
+from astroid.typing import InferenceErrorInfo
+
+if TYPE_CHECKING:
+    from astroid.objects import Property
 
 # Prevents circular imports
 objects = util.lazy_import("objects")
 
 
+_FunctionDefT = TypeVar("_FunctionDefT", bound=nodes.FunctionDef)
+
+
 # .infer method ###############################################################
 
 
@@ -317,6 +312,8 @@ def infer_attribute(self, context=None):
 
         if not context:
             context = InferenceContext()
+        else:
+            context = copy_context(context)
 
         old_boundnode = context.boundnode
         try:
@@ -834,7 +831,7 @@ def _do_compare(
     >>> _do_compare([1, 3], '<=', [2, 4])
     util.Uninferable
     """
-    retval = None
+    retval: Union[None, bool] = None
     if op in UNINFERABLE_OPS:
         return util.Uninferable
     op_func = COMPARE_OPS[op]
@@ -859,14 +856,15 @@ def _do_compare(
             return util.Uninferable
             # (or both, but "True | False" is basically the same)
 
+    assert retval is not None
     return retval  # it was all the same value
 
 
 def _infer_compare(
     self: nodes.Compare, context: Optional[InferenceContext] = None
-) -> Any:
+) -> Iterator[Union[nodes.Const, Type[util.Uninferable]]]:
     """Chained comparison inference logic."""
-    retval = True
+    retval: Union[bool, Type[util.Uninferable]] = True
 
     ops = self.ops
     left_node = self.left
@@ -884,7 +882,7 @@ def _infer_compare(
             break  # short-circuit
         lhs = rhs  # continue
     if retval is util.Uninferable:
-        yield retval
+        yield retval  # type: ignore[misc]
     else:
         yield nodes.Const(retval)
 
@@ -1041,8 +1039,10 @@ nodes.IfExp._infer = infer_ifexp  # type: ignore[assignment]
 
 # pylint: disable=dangerous-default-value
 @wrapt.decorator
-def _cached_generator(func, instance, args, kwargs, _cache={}):  # noqa: B006
-    node = args[0]
+def _cached_generator(
+    func, instance: _FunctionDefT, args, kwargs, _cache={}  # noqa: B006
+):
+    node = instance
     try:
         return iter(_cache[func, id(node)])
     except KeyError:
@@ -1059,22 +1059,23 @@ def _cached_generator(func, instance, args, kwargs, _cache={}):  # noqa: B006
 # are mutated with a new instance of the property. This is why we cache the result
 # of the function's inference.
 @_cached_generator
-def infer_functiondef(self, context=None):
+def infer_functiondef(
+    self: _FunctionDefT, context: Optional[InferenceContext] = None
+) -> Generator[Union["Property", _FunctionDefT], None, InferenceErrorInfo]:
     if not self.decorators or not bases._is_property(self):
         yield self
-        return dict(node=self, context=context)
+        return InferenceErrorInfo(node=self, context=context)
 
     prop_func = objects.Property(
         function=self,
         name=self.name,
-        doc=self.doc,
         lineno=self.lineno,
         parent=self.parent,
         col_offset=self.col_offset,
     )
-    prop_func.postinit(body=[], args=self.args)
+    prop_func.postinit(body=[], args=self.args, doc_node=self.doc_node)
     yield prop_func
-    return dict(node=self, context=context)
+    return InferenceErrorInfo(node=self, context=context)
 
 
 nodes.FunctionDef._infer = infer_functiondef  # type: ignore[assignment]
diff --git a/astroid/inference_tip.py b/astroid/inference_tip.py
index 2a7adcd..f74ff23 100644
--- a/astroid/inference_tip.py
+++ b/astroid/inference_tip.py
@@ -1,5 +1,6 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Transform utilities (filters and decorator)"""
 
@@ -7,12 +8,18 @@ import typing
 
 import wrapt
 
-from astroid.exceptions import InferenceOverwriteError
+from astroid import bases, util
+from astroid.exceptions import InferenceOverwriteError, UseInferenceDefault
 from astroid.nodes import NodeNG
+from astroid.typing import InferFn
 
-InferFn = typing.Callable[..., typing.Any]
+InferOptions = typing.Union[
+    NodeNG, bases.Instance, bases.UnboundMethod, typing.Type[util.Uninferable]
+]
 
-_cache: typing.Dict[typing.Tuple[InferFn, NodeNG], typing.Any] = {}
+_cache: typing.Dict[
+    typing.Tuple[InferFn, NodeNG], typing.Optional[typing.List[InferOptions]]
+] = {}
 
 
 def clear_inference_tip_cache():
@@ -21,13 +28,21 @@ def clear_inference_tip_cache():
 
 
 @wrapt.decorator
-def _inference_tip_cached(func, instance, args, kwargs):
+def _inference_tip_cached(
+    func: InferFn, instance: None, args: typing.Any, kwargs: typing.Any
+) -> typing.Iterator[InferOptions]:
     """Cache decorator used for inference tips"""
     node = args[0]
     try:
         result = _cache[func, node]
+        # If through recursion we end up trying to infer the same
+        # func + node we raise here.
+        if result is None:
+            raise UseInferenceDefault()
     except KeyError:
+        _cache[func, node] = None
         result = _cache[func, node] = list(func(*args, **kwargs))
+        assert result
     return iter(result)
 
 
diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py
index 57bab9f..3514959 100644
--- a/astroid/interpreter/_import/spec.py
+++ b/astroid/interpreter/_import/spec.py
@@ -1,28 +1,17 @@
-# Copyright (c) 2016-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2017 Chris Philip <chrisp533@gmail.com>
-# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2017 ioanatia <ioanatia@users.noreply.github.com>
-# Copyright (c) 2017 Calen Pennington <cale@edx.org>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Peter Kolbus <peter.kolbus@gmail.com>
-# Copyright (c) 2020 Raphael Gaschignard <raphael@rtpg.co>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 DudeNr33 <3929834+DudeNr33@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import abc
 import collections
 import enum
 import importlib.machinery
+import importlib.util
 import os
 import sys
 import zipimport
 from functools import lru_cache
+from pathlib import Path
 
 from . import util
 
@@ -160,6 +149,21 @@ class ImportlibFinder(Finder):
                 for p in sys.path
                 if os.path.isdir(os.path.join(p, *processed))
             ]
+        elif spec.name == "distutils":
+            # virtualenv below 20.0 patches distutils in an unexpected way
+            # so we just find the location of distutils that will be
+            # imported to avoid spurious import-error messages
+            # https://github.com/PyCQA/pylint/issues/5645
+            # A regression test to create this scenario exists in release-tests.yml
+            # and can be triggered manually from GitHub Actions
+            distutils_spec = importlib.util.find_spec("distutils")
+            if distutils_spec and distutils_spec.origin:
+                origin_path = Path(
+                    distutils_spec.origin
+                )  # e.g. .../distutils/__init__.py
+                path = [str(origin_path.parent)]  # e.g. .../distutils
+            else:
+                path = [spec.location]
         else:
             path = [spec.location]
         return path
diff --git a/astroid/interpreter/_import/util.py b/astroid/interpreter/_import/util.py
index e3ccf25..ce3da7e 100644
--- a/astroid/interpreter/_import/util.py
+++ b/astroid/interpreter/_import/util.py
@@ -1,7 +1,6 @@
-# Copyright (c) 2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Neil Girdhar <mistersheik@gmail.com>
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 try:
     import pkg_resources
diff --git a/astroid/interpreter/dunder_lookup.py b/astroid/interpreter/dunder_lookup.py
index 7ff0de5..b0c7ae5 100644
--- a/astroid/interpreter/dunder_lookup.py
+++ b/astroid/interpreter/dunder_lookup.py
@@ -1,8 +1,6 @@
-# Copyright (c) 2016-2018 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Contains logic for retrieving special methods.
 
diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py
index 359935b..cf9227b 100644
--- a/astroid/interpreter/objectmodel.py
+++ b/astroid/interpreter/objectmodel.py
@@ -1,16 +1,7 @@
-# Copyright (c) 2016-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2017 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2017 Calen Pennington <cale@edx.org>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """
 Data object model, as per https://docs.python.org/3/reference/datamodel.html.
 
@@ -34,8 +25,7 @@ import itertools
 import os
 import pprint
 import types
-from functools import lru_cache
-from typing import TYPE_CHECKING, Optional
+from typing import TYPE_CHECKING, List, Optional
 
 import astroid
 from astroid import util
@@ -50,6 +40,7 @@ if TYPE_CHECKING:
     from astroid.objects import Property
 
 IMPL_PREFIX = "attr_"
+LEN_OF_IMPL_PREFIX = len(IMPL_PREFIX)
 
 
 def _dunder_dict(instance, attributes):
@@ -109,12 +100,9 @@ class ObjectModel:
     def __contains__(self, name):
         return name in self.attributes()
 
-    @lru_cache(maxsize=None)
-    def attributes(self):
+    def attributes(self) -> List[str]:
         """Get the attributes which are exported by this object model."""
-        return [
-            obj[len(IMPL_PREFIX) :] for obj in dir(self) if obj.startswith(IMPL_PREFIX)
-        ]
+        return [o[LEN_OF_IMPL_PREFIX:] for o in dir(self) if o.startswith(IMPL_PREFIX)]
 
     def lookup(self, name):
         """Look up the given *name* in the current model
@@ -122,7 +110,6 @@ class ObjectModel:
         It should return an AST or an interpreter object,
         but if the name is not found, then an AttributeInferenceError will be raised.
         """
-
         if name in self.attributes():
             return getattr(self, IMPL_PREFIX + name)
         raise AttributeInferenceError(target=self._instance, attribute=name)
@@ -163,7 +150,10 @@ class ModuleModel(ObjectModel):
 
     @property
     def attr___doc__(self):
-        return node_classes.Const(value=self._instance.doc, parent=self._instance)
+        return node_classes.Const(
+            value=getattr(self._instance.doc_node, "value", None),
+            parent=self._instance,
+        )
 
     @property
     def attr___file__(self):
@@ -209,7 +199,10 @@ class FunctionModel(ObjectModel):
 
     @property
     def attr___doc__(self):
-        return node_classes.Const(value=self._instance.doc, parent=self._instance)
+        return node_classes.Const(
+            value=getattr(self._instance.doc_node, "value", None),
+            parent=self._instance,
+        )
 
     @property
     def attr___qualname__(self):
@@ -332,13 +325,18 @@ class FunctionModel(ObjectModel):
                 # class where it will be bound.
                 new_func = func.__class__(
                     name=func.name,
-                    doc=func.doc,
                     lineno=func.lineno,
                     col_offset=func.col_offset,
                     parent=func.parent,
                 )
                 # pylint: disable=no-member
-                new_func.postinit(func.args, func.body, func.decorators, func.returns)
+                new_func.postinit(
+                    func.args,
+                    func.body,
+                    func.decorators,
+                    func.returns,
+                    doc_node=func.doc_node,
+                )
 
                 # Build a proper bound method that points to our newly built function.
                 proxy = bases.UnboundMethod(new_func)
@@ -424,7 +422,7 @@ class ClassModel(ObjectModel):
 
     @property
     def attr___doc__(self):
-        return node_classes.Const(self._instance.doc)
+        return node_classes.Const(getattr(self._instance.doc_node, "value", None))
 
     @property
     def attr___mro__(self):
@@ -584,7 +582,8 @@ class GeneratorModel(FunctionModel):
     @property
     def attr___doc__(self):
         return node_classes.Const(
-            value=self._instance.parent.doc, parent=self._instance
+            value=getattr(self._instance.parent.doc_node, "value", None),
+            parent=self._instance,
         )
 
 
@@ -620,7 +619,7 @@ class InstanceModel(ObjectModel):
 
     @property
     def attr___doc__(self):
-        return node_classes.Const(self._instance.doc)
+        return node_classes.Const(getattr(self._instance.doc_node, "value", None))
 
     @property
     def attr___dict__(self):
diff --git a/astroid/manager.py b/astroid/manager.py
index ce5005c..950842e 100644
--- a/astroid/manager.py
+++ b/astroid/manager.py
@@ -1,28 +1,6 @@
-# Copyright (c) 2006-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 BioGeek <jeroen.vangoey@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2017 Iva Miholic <ivamiho@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2019 Raphael Gaschignard <raphael@makeleaps.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Raphael Gaschignard <raphael@rtpg.co>
-# Copyright (c) 2020 Anubhav <35621759+anubh-v@users.noreply.github.com>
-# Copyright (c) 2020 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 grayjk <grayjk@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-# Copyright (c) 2021 DudeNr33 <3929834+DudeNr33@users.noreply.github.com>
-# Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """astroid manager: avoid multiple astroid build of a same module when
 possible by providing a class responsible to get astroid representation
diff --git a/astroid/mixins.py b/astroid/mixins.py
index 097fd1e..ea68aff 100644
--- a/astroid/mixins.py
+++ b/astroid/mixins.py
@@ -1,22 +1,11 @@
-# Copyright (c) 2010-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """This module contains some mixins for the different nodes.
 """
 import itertools
+import sys
 from typing import TYPE_CHECKING, Optional
 
 from astroid import decorators
@@ -25,11 +14,16 @@ from astroid.exceptions import AttributeInferenceError
 if TYPE_CHECKING:
     from astroid import nodes
 
+if sys.version_info >= (3, 8) or TYPE_CHECKING:
+    from functools import cached_property
+else:
+    from astroid.decorators import cachedproperty as cached_property
+
 
 class BlockRangeMixIn:
     """override block range"""
 
-    @decorators.cachedproperty
+    @cached_property
     def blockstart_tolineno(self):
         return self.lineno
 
@@ -134,7 +128,7 @@ class MultiLineBlockMixin:
     Assign nodes, etc.
     """
 
-    @decorators.cachedproperty
+    @cached_property
     def _multi_line_blocks(self):
         return tuple(getattr(self, field) for field in self._multi_line_block_fields)
 
diff --git a/astroid/modutils.py b/astroid/modutils.py
index b20a184..01135ef 100644
--- a/astroid/modutils.py
+++ b/astroid/modutils.py
@@ -1,31 +1,6 @@
-# Copyright (c) 2014-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2014 Denis Laxalde <denis.laxalde@logilab.fr>
-# Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2015 Radosław Ganczarek <radoslaw@ganczarek.in>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Mario Corchero <mcorcherojim@bloomberg.net>
-# Copyright (c) 2018 Mario Corchero <mariocj89@gmail.com>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2019 markmcclain <markmcclain@users.noreply.github.com>
-# Copyright (c) 2019 BasPH <BasPH@users.noreply.github.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Peter Kolbus <peter.kolbus@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Keichi Takahashi <hello@keichi.dev>
-# Copyright (c) 2021 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 DudeNr33 <3929834+DudeNr33@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Python modules manipulation utility functions.
 
@@ -39,28 +14,20 @@
 :var BUILTIN_MODULES: dictionary with builtin module names has key
 """
 
-# We disable the import-error so pylint can work without distutils installed.
-# pylint: disable=no-name-in-module,useless-suppression
-
 import importlib
 import importlib.machinery
 import importlib.util
 import itertools
 import os
-import platform
 import sys
+import sysconfig
 import types
-from distutils.errors import DistutilsPlatformError  # pylint: disable=import-error
-from distutils.sysconfig import get_python_lib  # pylint: disable=import-error
+from pathlib import Path
 from typing import Dict, Set
 
+from astroid.const import IS_JYTHON, IS_PYPY
 from astroid.interpreter._import import spec, util
 
-# distutils is replaced by virtualenv with a module that does
-# weird path manipulations in order to get to the
-# real distutils module.
-
-
 if sys.platform.startswith("win"):
     PY_SOURCE_EXTS = ("py", "pyw")
     PY_COMPILED_EXTS = ("dll", "pyd")
@@ -69,22 +36,9 @@ else:
     PY_COMPILED_EXTS = ("so",)
 
 
-try:
-    # The explicit sys.prefix is to work around a patch in virtualenv that
-    # replaces the 'real' sys.prefix (i.e. the location of the binary)
-    # with the prefix from which the virtualenv was created. This throws
-    # off the detection logic for standard library modules, thus the
-    # workaround.
-    STD_LIB_DIRS = {
-        get_python_lib(standard_lib=True, prefix=sys.prefix),
-        # Take care of installations where exec_prefix != prefix.
-        get_python_lib(standard_lib=True, prefix=sys.exec_prefix),
-        get_python_lib(standard_lib=True),
-    }
-# get_python_lib(standard_lib=1) is not available on pypy, set STD_LIB_DIR to
-# non-valid path, see https://bugs.pypy.org/issue1164
-except DistutilsPlatformError:
-    STD_LIB_DIRS = set()
+# TODO: Adding `platstdlib` is a fix for a workaround in virtualenv. At some point we should
+# revisit whether this is still necessary. See https://github.com/PyCQA/astroid/pull/1323.
+STD_LIB_DIRS = {sysconfig.get_path("stdlib"), sysconfig.get_path("platstdlib")}
 
 if os.name == "nt":
     STD_LIB_DIRS.add(os.path.join(sys.prefix, "dlls"))
@@ -103,27 +57,20 @@ if os.name == "nt":
         except AttributeError:
             pass
 
-if platform.python_implementation() == "PyPy":
-    # The get_python_lib(standard_lib=True) function does not give valid
-    # result with pypy in a virtualenv.
-    # In a virtual environment, with CPython implementation the call to this function returns a path toward
-    # the binary (its libraries) which has been used to create the virtual environment.
-    # Not with pypy implementation.
-    # The only way to retrieve such information is to use the sys.base_prefix hint.
-    # It's worth noticing that under CPython implementation the return values of
-    # get_python_lib(standard_lib=True) and get_python_lib(santdard_lib=True, prefix=sys.base_prefix)
-    # are the same.
-    # In the lines above, we could have replace the call to get_python_lib(standard=True)
-    # with the one using prefix=sys.base_prefix but we prefer modifying only what deals with pypy.
-    STD_LIB_DIRS.add(get_python_lib(standard_lib=True, prefix=sys.base_prefix))
-    _root = os.path.join(sys.prefix, "lib_pypy")
-    STD_LIB_DIRS.add(_root)
-    try:
-        # real_prefix is defined when running inside virtualenv.
-        STD_LIB_DIRS.add(os.path.join(sys.base_prefix, "lib_pypy"))
-    except AttributeError:
-        pass
-    del _root
+if IS_PYPY and sys.version_info < (3, 8):
+    # PyPy stores the stdlib in two places: sys.prefix/lib_pypy and sys.prefix/lib-python/3
+    # sysconfig.get_path on PyPy returns the first, but without an underscore so we patch this manually.
+    # Beginning with 3.8 the stdlib is only stored in: sys.prefix/pypy{py_version_short}
+    STD_LIB_DIRS.add(str(Path(sysconfig.get_path("stdlib")).parent / "lib_pypy"))
+    STD_LIB_DIRS.add(str(Path(sysconfig.get_path("stdlib")).parent / "lib-python/3"))
+
+    # TODO: This is a fix for a workaround in virtualenv. At some point we should revisit
+    # whether this is still necessary. See https://github.com/PyCQA/astroid/pull/1324.
+    STD_LIB_DIRS.add(str(Path(sysconfig.get_path("platstdlib")).parent / "lib_pypy"))
+    STD_LIB_DIRS.add(
+        str(Path(sysconfig.get_path("platstdlib")).parent / "lib-python/3")
+    )
+
 if os.name == "posix":
     # Need the real prefix if we're in a virtualenv, otherwise
     # the usual one will do.
@@ -139,7 +86,7 @@ if os.name == "posix":
         return os.path.join(prefix, path, base_python)
 
     STD_LIB_DIRS.add(_posix_path("lib"))
-    if sys.maxsize > 2 ** 32:
+    if sys.maxsize > 2**32:
         # This tries to fix a problem with /usr/lib64 builds,
         # where systems are running both 32-bit and 64-bit code
         # on the same machine, which reflects into the places where
@@ -149,8 +96,7 @@ if os.name == "posix":
         # https://github.com/PyCQA/pylint/issues/712#issuecomment-163178753
         STD_LIB_DIRS.add(_posix_path("lib64"))
 
-EXT_LIB_DIRS = {get_python_lib(), get_python_lib(True)}
-IS_JYTHON = platform.python_implementation() == "Jython"
+EXT_LIB_DIRS = {sysconfig.get_path("purelib"), sysconfig.get_path("platlib")}
 BUILTIN_MODULES = dict.fromkeys(sys.builtin_module_names, True)
 
 
diff --git a/astroid/node_classes.py b/astroid/node_classes.py
index 3288f56..3711309 100644
--- a/astroid/node_classes.py
+++ b/astroid/node_classes.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 # pylint: disable=unused-import
 
 import warnings
diff --git a/astroid/nodes/__init__.py b/astroid/nodes/__init__.py
index f284d6f..0a98ed1 100644
--- a/astroid/nodes/__init__.py
+++ b/astroid/nodes/__init__.py
@@ -1,18 +1,6 @@
-# Copyright (c) 2006-2011, 2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com>
-# Copyright (c) 2017 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2017 rr- <rr-@sakuya.pl>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Every available node class.
 
@@ -121,6 +109,7 @@ from astroid.nodes.scoped_nodes import (
     function_to_method,
     get_wrapping_class,
 )
+from astroid.nodes.utils import Position
 
 _BaseContainer = BaseContainer  # TODO Remove for astroid 3.0
 
@@ -288,6 +277,7 @@ __all__ = (
     "NodeNG",
     "Nonlocal",
     "Pass",
+    "Position",
     "Raise",
     "Return",
     "Set",
diff --git a/astroid/nodes/as_string.py b/astroid/nodes/as_string.py
index 2f2874c..2e2bdcf 100644
--- a/astroid/nodes/as_string.py
+++ b/astroid/nodes/as_string.py
@@ -1,30 +1,12 @@
-# Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
-# Copyright (c) 2013-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 rr- <rr-@sakuya.pl>
-# Copyright (c) 2018 Serhiy Storchaka <storchaka@gmail.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2019 Alex Hall <alex.mojaki@gmail.com>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """This module renders Astroid nodes as string"""
-from typing import TYPE_CHECKING, List
+from typing import TYPE_CHECKING, List, Optional
 
 if TYPE_CHECKING:
+    from astroid.nodes import Const
     from astroid.nodes.node_classes import (
         Match,
         MatchAs,
@@ -56,9 +38,14 @@ class AsStringVisitor:
         """Makes this visitor behave as a simple function"""
         return node.accept(self).replace(DOC_NEWLINE, "\n")
 
-    def _docs_dedent(self, doc):
+    def _docs_dedent(self, doc_node: Optional["Const"]) -> str:
         """Stop newlines in docs being indented by self._stmt_list"""
-        return '\n{}"""{}"""'.format(self.indent, doc.replace("\n", DOC_NEWLINE))
+        if not doc_node:
+            return ""
+
+        return '\n{}"""{}"""'.format(
+            self.indent, doc_node.value.replace("\n", DOC_NEWLINE)
+        )
 
     def _stmt_list(self, stmts, indent=True):
         """return a list of nodes to string"""
@@ -182,7 +169,7 @@ class AsStringVisitor:
             args.append("metaclass=" + node._metaclass.accept(self))
         args += [n.accept(self) for n in node.keywords]
         args = f"({', '.join(args)})" if args else ""
-        docs = self._docs_dedent(node.doc) if node.doc else ""
+        docs = self._docs_dedent(node.doc_node)
         return "\n\n{}class {}{}:{}\n{}\n".format(
             decorate, node.name, args, docs, self._stmt_list(node.body)
         )
@@ -327,7 +314,7 @@ class AsStringVisitor:
     def handle_functiondef(self, node, keyword):
         """return a (possibly async) function definition node as string"""
         decorate = node.decorators.accept(self) if node.decorators else ""
-        docs = self._docs_dedent(node.doc) if node.doc else ""
+        docs = self._docs_dedent(node.doc_node)
         trailer = ":"
         if node.returns:
             return_annotation = " -> " + node.returns.as_string()
@@ -416,7 +403,7 @@ class AsStringVisitor:
 
     def visit_module(self, node):
         """return an astroid.Module node as string"""
-        docs = f'"""{node.doc}"""\n\n' if node.doc else ""
+        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):
diff --git a/astroid/nodes/const.py b/astroid/nodes/const.py
index 8f1b6ab..6782cc3 100644
--- a/astroid/nodes/const.py
+++ b/astroid/nodes/const.py
@@ -1,6 +1,6 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
-# For details: https://github.com/PyCQA/astroid/blob/master/LICENSE
-
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 OP_PRECEDENCE = {
     op: precedence
diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py
index 5e6afb2..c941690 100644
--- a/astroid/nodes/node_classes.py
+++ b/astroid/nodes/node_classes.py
@@ -1,40 +1,6 @@
-# Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
-# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
-# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2021 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2016-2017 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2016 Dave Baum <dbaum@google.com>
-# Copyright (c) 2017-2020 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 rr- <rr-@sakuya.pl>
-# Copyright (c) 2018, 2021 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com>
-# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
-# Copyright (c) 2019 kavins14 <kavin.singh@mail.utoronto.ca>
-# Copyright (c) 2019 kavins14 <kavinsingh@hotmail.com>
-# Copyright (c) 2020 Raphael Gaschignard <raphael@rtpg.co>
-# Copyright (c) 2020 Bryce Guinta <bryce.guinta@protonmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Kian Meng, Ang <kianmeng.ang@gmail.com>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 Alphadelta14 <alpha@alphaservcomputing.solutions>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-# Copyright (c) 2021 Federico Bond <federicobond@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Module for some node classes. More nodes in scoped_nodes.py"""
 
@@ -44,7 +10,17 @@ import sys
 import typing
 import warnings
 from functools import lru_cache
-from typing import TYPE_CHECKING, Any, Callable, Generator, Optional, TypeVar, Union
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    ClassVar,
+    Generator,
+    Optional,
+    Type,
+    TypeVar,
+    Union,
+)
 
 from astroid import decorators, mixins, util
 from astroid.bases import Instance, _infer_stmts
@@ -70,6 +46,12 @@ if TYPE_CHECKING:
     from astroid import nodes
     from astroid.nodes import LocalsDictNodeNG
 
+if sys.version_info >= (3, 8) or TYPE_CHECKING:
+    # pylint: disable-next=ungrouped-imports
+    from functools import cached_property
+else:
+    from astroid.decorators import cachedproperty as cached_property
+
 
 def _is_const(value):
     return isinstance(value, tuple(CONST_CLS))
@@ -385,8 +367,8 @@ class BaseContainer(
 class LookupMixIn:
     """Mixin to look up a name in the right scope."""
 
-    @lru_cache(maxsize=None)
-    def lookup(self, name):
+    @lru_cache(maxsize=None)  # pylint: disable=cache-max-size-none  # noqa
+    def lookup(self, name: str) -> typing.Tuple[str, typing.List[NodeNG]]:
         """Lookup where the given variable is assigned.
 
         The lookup starts from self's scope. If self is not a frame itself
@@ -394,12 +376,10 @@ class LookupMixIn:
         filtered to remove ignorable statements according to self's location.
 
         :param name: The name of the variable to find assignments for.
-        :type name: str
 
         :returns: The scope node and the list of assignments associated to the
             given name according to the scope where it has been found (locals,
             globals or builtin).
-        :rtype: tuple(str, list(NodeNG))
         """
         return self.scope().scope_lookup(self, name)
 
@@ -478,7 +458,7 @@ class AssignName(
             parent=parent,
         )
 
-    assigned_stmts: AssignedStmtsCall["AssignName"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["AssignName"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -669,7 +649,7 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
         self.kwarg: Optional[str] = kwarg  # can be None
         """The name of the variable length keyword arguments."""
 
-        self.args: typing.Optional[typing.List[AssignName]]
+        self.args: Optional[typing.List[AssignName]]
         """The names of the required arguments.
 
         Can be None if the associated function does not have a retrievable
@@ -804,7 +784,7 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
         if type_comment_posonlyargs is not None:
             self.type_comment_posonlyargs = type_comment_posonlyargs
 
-    assigned_stmts: AssignedStmtsCall["Arguments"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["Arguments"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -814,7 +794,7 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
             return name
         return None
 
-    @decorators.cachedproperty
+    @cached_property
     def fromlineno(self):
         """The first line that this node appears on in the source code.
 
@@ -823,7 +803,7 @@ class Arguments(mixins.AssignTypeMixin, NodeNG):
         lineno = super().fromlineno
         return max(lineno, self.parent.fromlineno or 0)
 
-    @decorators.cachedproperty
+    @cached_property
     def arguments(self):
         """Get all the arguments for this node, including positional only and positional and keyword"""
         return list(itertools.chain((self.posonlyargs or ()), self.args or ()))
@@ -1062,7 +1042,7 @@ class AssignAttr(mixins.ParentAssignTypeMixin, NodeNG):
         """
         self.expr = expr
 
-    assigned_stmts: AssignedStmtsCall["AssignAttr"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["AssignAttr"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -1210,7 +1190,7 @@ class Assign(mixins.AssignTypeMixin, Statement):
         self.value = value
         self.type_annotation = type_annotation
 
-    assigned_stmts: AssignedStmtsCall["Assign"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["Assign"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -1307,7 +1287,7 @@ class AnnAssign(mixins.AssignTypeMixin, Statement):
         self.value = value
         self.simple = simple
 
-    assigned_stmts: AssignedStmtsCall["AnnAssign"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["AnnAssign"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -1393,7 +1373,7 @@ class AugAssign(mixins.AssignTypeMixin, Statement):
         self.target = target
         self.value = value
 
-    assigned_stmts: AssignedStmtsCall["AugAssign"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["AugAssign"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -1864,7 +1844,7 @@ class Comprehension(NodeNG):
             self.ifs = ifs
         self.is_async = is_async
 
-    assigned_stmts: AssignedStmtsCall["Comprehension"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["Comprehension"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -1919,7 +1899,7 @@ class Const(mixins.NoChildrenMixin, NodeNG, Instance):
 
     def __init__(
         self,
-        value: typing.Any,
+        value: Any,
         lineno: Optional[int] = None,
         col_offset: Optional[int] = None,
         parent: Optional[NodeNG] = None,
@@ -1945,7 +1925,7 @@ class Const(mixins.NoChildrenMixin, NodeNG, Instance):
         :param end_col_offset: The end column this node appears on in the
             source code. Note: This is after the last symbol.
         """
-        self.value: typing.Any = value
+        self.value: Any = value
         """The value that the constant represents."""
 
         self.kind: Optional[str] = kind  # can be None
@@ -2556,7 +2536,7 @@ class ExceptHandler(mixins.MultiLineBlockMixin, mixins.AssignTypeMixin, Statemen
             parent=parent,
         )
 
-    assigned_stmts: AssignedStmtsCall["ExceptHandler"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["ExceptHandler"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -2591,7 +2571,7 @@ class ExceptHandler(mixins.MultiLineBlockMixin, mixins.AssignTypeMixin, Statemen
         if body is not None:
             self.body = body
 
-    @decorators.cachedproperty
+    @cached_property
     def blockstart_tolineno(self):
         """The line on which the beginning of this block ends.
 
@@ -2719,12 +2699,12 @@ class For(
             self.orelse = orelse
         self.type_annotation = type_annotation
 
-    assigned_stmts: AssignedStmtsCall["For"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["For"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
 
-    @decorators.cachedproperty
+    @cached_property
     def blockstart_tolineno(self):
         """The line on which the beginning of this block ends.
 
@@ -3083,7 +3063,7 @@ class If(mixins.MultiLineBlockMixin, mixins.BlockRangeMixIn, Statement):
         if isinstance(self.parent, If) and self in self.parent.orelse:
             self.is_orelse = True
 
-    @decorators.cachedproperty
+    @cached_property
     def blockstart_tolineno(self):
         """The line on which the beginning of this block ends.
 
@@ -3424,7 +3404,7 @@ class List(BaseContainer):
             parent=parent,
         )
 
-    assigned_stmts: AssignedStmtsCall["List"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["List"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -3752,7 +3732,7 @@ class Slice(NodeNG):
             return const
         return attr
 
-    @decorators.cachedproperty
+    @cached_property
     def _proxied(self):
         builtins = AstroidManager().builtins_module
         return builtins.getattr("slice")[0]
@@ -3855,7 +3835,7 @@ class Starred(mixins.ParentAssignTypeMixin, NodeNG):
         """
         self.value = value
 
-    assigned_stmts: AssignedStmtsCall["Starred"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["Starred"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -4084,7 +4064,7 @@ class TryFinally(mixins.MultiLineBlockMixin, mixins.BlockRangeMixIn, Statement):
         :param end_col_offset: The end column this node appears on in the
             source code. Note: This is after the last symbol.
         """
-        self.body: typing.Union[typing.List[TryExcept], typing.List[NodeNG]] = []
+        self.body: typing.List[Union[NodeNG, TryExcept]] = []
         """The try-except that the finally is attached to."""
 
         self.finalbody: typing.List[NodeNG] = []
@@ -4100,7 +4080,7 @@ class TryFinally(mixins.MultiLineBlockMixin, mixins.BlockRangeMixIn, Statement):
 
     def postinit(
         self,
-        body: typing.Union[typing.List[TryExcept], typing.List[NodeNG], None] = None,
+        body: Optional[typing.List[Union[NodeNG, TryExcept]]] = None,
         finalbody: Optional[typing.List[NodeNG]] = None,
     ) -> None:
         """Do some setup after initialisation.
@@ -4186,7 +4166,7 @@ class Tuple(BaseContainer):
             parent=parent,
         )
 
-    assigned_stmts: AssignedStmtsCall["Tuple"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["Tuple"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -4374,7 +4354,7 @@ class While(mixins.MultiLineBlockMixin, mixins.BlockRangeMixIn, Statement):
         if orelse is not None:
             self.orelse = orelse
 
-    @decorators.cachedproperty
+    @cached_property
     def blockstart_tolineno(self):
         """The line on which the beginning of this block ends.
 
@@ -4485,12 +4465,12 @@ class With(
             self.body = body
         self.type_annotation = type_annotation
 
-    assigned_stmts: AssignedStmtsCall["With"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["With"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
 
-    @decorators.cachedproperty
+    @cached_property
     def blockstart_tolineno(self):
         """The line on which the beginning of this block ends.
 
@@ -4793,7 +4773,7 @@ class NamedExpr(mixins.AssignTypeMixin, NodeNG):
         self.target = target
         self.value = value
 
-    assigned_stmts: AssignedStmtsCall["NamedExpr"]
+    assigned_stmts: ClassVar[AssignedStmtsCall["NamedExpr"]]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
     """
@@ -4883,12 +4863,12 @@ class EvaluatedObject(NodeNG):
     _other_fields = ("value",)
 
     def __init__(
-        self, original: NodeNG, value: typing.Union[NodeNG, util.Uninferable]
+        self, original: NodeNG, value: Union[NodeNG, Type[util.Uninferable]]
     ) -> None:
         self.original: NodeNG = original
         """The original node that has already been evaluated"""
 
-        self.value: typing.Union[NodeNG, util.Uninferable] = value
+        self.value: Union[NodeNG, Type[util.Uninferable]] = value
         """The inferred value"""
 
         super().__init__(
@@ -5160,14 +5140,16 @@ class MatchMapping(mixins.AssignTypeMixin, Pattern):
         self.patterns = patterns
         self.rest = rest
 
-    assigned_stmts: Callable[
-        [
-            "MatchMapping",
-            AssignName,
-            Optional[InferenceContext],
-            Literal[None],
-        ],
-        Generator[NodeNG, None, None],
+    assigned_stmts: ClassVar[
+        Callable[
+            [
+                "MatchMapping",
+                AssignName,
+                Optional[InferenceContext],
+                None,
+            ],
+            Generator[NodeNG, None, None],
+        ]
     ]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
@@ -5265,14 +5247,16 @@ class MatchStar(mixins.AssignTypeMixin, Pattern):
     def postinit(self, *, name: Optional[AssignName]) -> None:
         self.name = name
 
-    assigned_stmts: Callable[
-        [
-            "MatchStar",
-            AssignName,
-            Optional[InferenceContext],
-            Literal[None],
-        ],
-        Generator[NodeNG, None, None],
+    assigned_stmts: ClassVar[
+        Callable[
+            [
+                "MatchStar",
+                AssignName,
+                Optional[InferenceContext],
+                None,
+            ],
+            Generator[NodeNG, None, None],
+        ]
     ]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
@@ -5334,14 +5318,16 @@ class MatchAs(mixins.AssignTypeMixin, Pattern):
         self.pattern = pattern
         self.name = name
 
-    assigned_stmts: Callable[
-        [
-            "MatchAs",
-            AssignName,
-            Optional[InferenceContext],
-            Literal[None],
-        ],
-        Generator[NodeNG, None, None],
+    assigned_stmts: ClassVar[
+        Callable[
+            [
+                "MatchAs",
+                AssignName,
+                Optional[InferenceContext],
+                None,
+            ],
+            Generator[NodeNG, None, None],
+        ]
     ]
     """Returns the assigned statement (non inferred) according to the assignment type.
     See astroid/protocols.py for actual implementation.
diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py
index eb2a5fd..db249af 100644
--- a/astroid/nodes/node_ng.py
+++ b/astroid/nodes/node_ng.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import pprint
 import sys
 import typing
@@ -28,6 +32,8 @@ from astroid.exceptions import (
 from astroid.manager import AstroidManager
 from astroid.nodes.as_string import AsStringVisitor
 from astroid.nodes.const import OP_PRECEDENCE
+from astroid.nodes.utils import Position
+from astroid.typing import InferFn
 
 if TYPE_CHECKING:
     from astroid import nodes
@@ -37,6 +43,12 @@ if sys.version_info >= (3, 8):
 else:
     from typing_extensions import Literal
 
+if sys.version_info >= (3, 8) or TYPE_CHECKING:
+    # pylint: disable-next=ungrouped-imports
+    from functools import cached_property
+else:
+    # pylint: disable-next=ungrouped-imports
+    from astroid.decorators import cachedproperty as cached_property
 
 # Types for 'NodeNG.nodes_of_class()'
 T_Nodes = TypeVar("T_Nodes", bound="NodeNG")
@@ -77,7 +89,7 @@ class NodeNG:
     _other_other_fields: ClassVar[typing.Tuple[str, ...]] = ()
     """Attributes that contain AST-dependent fields."""
     # instance specific inference function infer(node, context)
-    _explicit_inference = None
+    _explicit_inference: Optional[InferFn] = None
 
     def __init__(
         self,
@@ -118,6 +130,12 @@ class NodeNG:
         Note: This is after the last symbol.
         """
 
+        self.position: Optional[Position] = None
+        """Position of keyword(s) and name. Used as fallback for block nodes
+        which might not provide good enough positional information.
+        E.g. ClassDef, FunctionDef.
+        """
+
     def infer(self, context=None, **kwargs):
         """Get a generator of the inferred values.
 
@@ -147,7 +165,7 @@ class NodeNG:
 
         if not context:
             # nodes_inferred?
-            yield from self._infer(context, **kwargs)
+            yield from self._infer(context=context, **kwargs)
             return
 
         key = (self, context.lookupname, context.callcontext, context.boundnode)
@@ -155,7 +173,7 @@ class NodeNG:
             yield from context.inferred[key]
             return
 
-        generator = self._infer(context, **kwargs)
+        generator = self._infer(context=context, **kwargs)
         results = []
 
         # Limit inference amount to help with performance issues with
@@ -163,7 +181,9 @@ class NodeNG:
         limit = AstroidManager().max_inferable_values
         for i, result in enumerate(generator):
             if i >= limit or (context.nodes_inferred > context.max_inferred):
-                yield util.Uninferable
+                uninferable = util.Uninferable
+                results.append(uninferable)
+                yield uninferable
                 break
             results.append(result)
             yield result
@@ -174,7 +194,7 @@ class NodeNG:
         context.inferred[key] = tuple(results)
         return
 
-    def _repr_name(self):
+    def _repr_name(self) -> str:
         """Get a name for nice representation.
 
         This is either :attr:`name`, :attr:`attrname`, or the empty string.
@@ -186,7 +206,7 @@ class NodeNG:
             return getattr(self, "name", "") or getattr(self, "attrname", "")
         return ""
 
-    def __str__(self):
+    def __str__(self) -> str:
         rname = self._repr_name()
         cname = type(self).__name__
         if rname:
@@ -212,7 +232,7 @@ class NodeNG:
             "fields": (",\n" + " " * alignment).join(result),
         }
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         rname = self._repr_name()
         if rname:
             string = "<%(cname)s.%(rname)s l.%(lineno)s at 0x%(id)x>"
@@ -246,7 +266,7 @@ class NodeNG:
         """An optimized version of list(get_children())[-1]"""
         for field in self._astroid_fields[::-1]:
             attr = getattr(self, field)
-            if not attr:  # None or empty listy / tuple
+            if not attr:  # None or empty list / tuple
                 continue
             if isinstance(attr, (list, tuple)):
                 return attr[-1]
@@ -274,7 +294,7 @@ class NodeNG:
 
     @overload
     def statement(
-        self, *, future: Literal[None] = ...
+        self, *, future: None = ...
     ) -> Union["nodes.Statement", "nodes.Module"]:
         ...
 
@@ -426,16 +446,18 @@ class NodeNG:
     # these are lazy because they're relatively expensive to compute for every
     # single node, and they rarely get looked at
 
-    @decorators.cachedproperty
+    @cached_property
     def fromlineno(self) -> Optional[int]:
         """The first line that this node appears on in the source code."""
         if self.lineno is None:
             return self._fixed_source_line()
         return self.lineno
 
-    @decorators.cachedproperty
+    @cached_property
     def tolineno(self) -> Optional[int]:
         """The last line that this node appears on in the source code."""
+        if self.end_lineno is not None:
+            return self.end_lineno
         if not self._astroid_fields:
             # can't have children
             last_child = None
@@ -746,6 +768,9 @@ class NodeNG:
                 result.append("\n")
                 result.append(cur_indent)
                 for field in fields[:-1]:
+                    # TODO: Remove this after removal of the 'doc' attribute
+                    if field == "doc":
+                        continue
                     result.append(f"{field}=")
                     _repr_tree(getattr(node, field), result, done, cur_indent, depth)
                     result.append(",\n")
diff --git a/astroid/nodes/scoped_nodes/__init__.py b/astroid/nodes/scoped_nodes/__init__.py
index 9c0463d..816bd83 100644
--- a/astroid/nodes/scoped_nodes/__init__.py
+++ b/astroid/nodes/scoped_nodes/__init__.py
@@ -1,28 +1,28 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """This module contains all classes that are considered a "scoped" node and anything related.
 A scope node is a node that opens a new local scope in the language definition:
 Module, ClassDef, FunctionDef (and Lambda, GeneratorExp, DictComp and SetComp to some extent).
 """
+
+from astroid.nodes.scoped_nodes.mixin import ComprehensionScope, LocalsDictNodeNG
 from astroid.nodes.scoped_nodes.scoped_nodes import (
     AsyncFunctionDef,
     ClassDef,
-    ComprehensionScope,
     DictComp,
     FunctionDef,
     GeneratorExp,
     Lambda,
     ListComp,
-    LocalsDictNodeNG,
     Module,
     SetComp,
     _is_metaclass,
-    builtin_lookup,
     function_to_method,
     get_wrapping_class,
 )
+from astroid.nodes.scoped_nodes.utils import builtin_lookup
 
 __all__ = (
     "AsyncFunctionDef",
diff --git a/astroid/nodes/scoped_nodes/mixin.py b/astroid/nodes/scoped_nodes/mixin.py
new file mode 100644
index 0000000..bb1e76f
--- /dev/null
+++ b/astroid/nodes/scoped_nodes/mixin.py
@@ -0,0 +1,171 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
+"""This module contains mixin classes for scoped nodes."""
+
+from typing import TYPE_CHECKING, Dict, List, TypeVar
+
+from astroid.filter_statements import _filter_stmts
+from astroid.nodes import node_classes, scoped_nodes
+from astroid.nodes.scoped_nodes.utils import builtin_lookup
+
+if TYPE_CHECKING:
+    from astroid import nodes
+
+_T = TypeVar("_T")
+
+
+class LocalsDictNodeNG(node_classes.LookupMixIn, node_classes.NodeNG):
+    """this class provides locals handling common to Module, FunctionDef
+    and ClassDef nodes, including a dict like interface for direct access
+    to locals information
+    """
+
+    # attributes below are set by the builder module or by raw factories
+
+    locals: Dict[str, List["nodes.NodeNG"]] = {}
+    """A map of the name of a local variable to the node defining the local."""
+
+    def qname(self):
+        """Get the 'qualified' name of the node.
+
+        For example: module.name, module.class.name ...
+
+        :returns: The qualified name.
+        :rtype: str
+        """
+        # pylint: disable=no-member; github.com/pycqa/astroid/issues/278
+        if self.parent is None:
+            return self.name
+        return f"{self.parent.frame(future=True).qname()}.{self.name}"
+
+    def scope(self: _T) -> _T:
+        """The first parent node defining a new scope.
+
+        :returns: The first parent scope node.
+        :rtype: Module or FunctionDef or ClassDef or Lambda or GenExpr
+        """
+        return self
+
+    def _scope_lookup(self, node, name, offset=0):
+        """XXX method for interfacing the scope lookup"""
+        try:
+            stmts = _filter_stmts(node, self.locals[name], self, offset)
+        except KeyError:
+            stmts = ()
+        if stmts:
+            return self, stmts
+
+        # Handle nested scopes: since class names do not extend to nested
+        # scopes (e.g., methods), we find the next enclosing non-class scope
+        pscope = self.parent and self.parent.scope()
+        while pscope is not None:
+            if not isinstance(pscope, scoped_nodes.ClassDef):
+                return pscope.scope_lookup(node, name)
+            pscope = pscope.parent and pscope.parent.scope()
+
+        # self is at the top level of a module, or is enclosed only by ClassDefs
+        return builtin_lookup(name)
+
+    def set_local(self, name, stmt):
+        """Define that the given name is declared in the given statement node.
+
+        .. seealso:: :meth:`scope`
+
+        :param name: The name that is being defined.
+        :type name: str
+
+        :param stmt: The statement that defines the given name.
+        :type stmt: NodeNG
+        """
+        # assert not stmt in self.locals.get(name, ()), (self, stmt)
+        self.locals.setdefault(name, []).append(stmt)
+
+    __setitem__ = set_local
+
+    def _append_node(self, child):
+        """append a child, linking it in the tree"""
+        # pylint: disable=no-member; depending by the class
+        # which uses the current class as a mixin or base class.
+        # It's rewritten in 2.0, so it makes no sense for now
+        # to spend development time on it.
+        self.body.append(child)
+        child.parent = self
+
+    def add_local_node(self, child_node, name=None):
+        """Append a child that should alter the locals of this scope node.
+
+        :param child_node: The child node that will alter locals.
+        :type child_node: NodeNG
+
+        :param name: The name of the local that will be altered by
+            the given child node.
+        :type name: str or None
+        """
+        if name != "__class__":
+            # add __class__ node as a child will cause infinite recursion later!
+            self._append_node(child_node)
+        self.set_local(name or child_node.name, child_node)
+
+    def __getitem__(self, item: str) -> "nodes.NodeNG":
+        """The first node the defines the given local.
+
+        :param item: The name of the locally defined object.
+
+        :raises KeyError: If the name is not defined.
+        """
+        return self.locals[item][0]
+
+    def __iter__(self):
+        """Iterate over the names of locals defined in this scoped node.
+
+        :returns: The names of the defined locals.
+        :rtype: iterable(str)
+        """
+        return iter(self.keys())
+
+    def keys(self):
+        """The names of locals defined in this scoped node.
+
+        :returns: The names of the defined locals.
+        :rtype: list(str)
+        """
+        return list(self.locals.keys())
+
+    def values(self):
+        """The nodes that define the locals in this scoped node.
+
+        :returns: The nodes that define locals.
+        :rtype: list(NodeNG)
+        """
+        # pylint: disable=consider-using-dict-items
+        # It look like this class override items/keys/values,
+        # probably not worth the headache
+        return [self[key] for key in self.keys()]
+
+    def items(self):
+        """Get the names of the locals and the node that defines the local.
+
+        :returns: The names of locals and their associated node.
+        :rtype: list(tuple(str, NodeNG))
+        """
+        return list(zip(self.keys(), self.values()))
+
+    def __contains__(self, name):
+        """Check if a local is defined in this scope.
+
+        :param name: The name of the local to check for.
+        :type name: str
+
+        :returns: True if this node has a local of the given name,
+            False otherwise.
+        :rtype: bool
+        """
+        return name in self.locals
+
+
+class ComprehensionScope(LocalsDictNodeNG):
+    """Scoping for different types of comprehensions."""
+
+    scope_lookup = LocalsDictNodeNG._scope_lookup
diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py
index 90c38f3..07ca28b 100644
--- a/astroid/nodes/scoped_nodes/scoped_nodes.py
+++ b/astroid/nodes/scoped_nodes/scoped_nodes.py
@@ -1,61 +1,24 @@
-# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
-# Copyright (c) 2011, 2013-2015 Google, Inc.
-# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2013 Phil Schaf <flying-sheep@web.de>
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Rene Zhang <rz99@cornell.edu>
-# Copyright (c) 2015 Philip Lorenz <philip@bithub.de>
-# Copyright (c) 2016-2017 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2017-2018 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 David Euresti <david@dropbox.com>
-# Copyright (c) 2018-2019, 2021 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2019 Peter de Blanc <peter@standard.ai>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Peter Kolbus <peter.kolbus@gmail.com>
-# Copyright (c) 2020 Tim Martin <tim@asymptotic.co.uk>
-# Copyright (c) 2020 Ram Rachum <ram@rachum.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Kian Meng, Ang <kianmeng.ang@gmail.com>
-# Copyright (c) 2021 Dmitry Shachnev <mitya57@users.noreply.github.com>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
-# Copyright (c) 2021 doranid <ddandd@gmail.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """
 This module contains the classes for "scoped" node, i.e. which are opening a
 new local scope in the language definition : Module, ClassDef, FunctionDef (and
 Lambda, GeneratorExp, DictComp and SetComp to some extent).
 """
-import builtins
 import io
 import itertools
 import os
 import sys
 import typing
 import warnings
-from typing import List, Optional, TypeVar, Union, overload
+from typing import TYPE_CHECKING, Dict, List, Optional, Set, TypeVar, Union, overload
 
 from astroid import bases
 from astroid import decorators as decorators_mod
 from astroid import mixins, util
-from astroid.const import PY39_PLUS
+from astroid.const import IS_PYPY, PY38, PY38_PLUS, PY39_PLUS
 from astroid.context import (
     CallContext,
     InferenceContext,
@@ -73,11 +36,13 @@ from astroid.exceptions import (
     StatementMissing,
     TooManyLevelsError,
 )
-from astroid.filter_statements import _filter_stmts
 from astroid.interpreter.dunder_lookup import lookup
 from astroid.interpreter.objectmodel import ClassModel, FunctionModel, ModuleModel
 from astroid.manager import AstroidManager
-from astroid.nodes import Arguments, Const, node_classes
+from astroid.nodes import Arguments, Const, NodeNG, node_classes
+from astroid.nodes.scoped_nodes.mixin import ComprehensionScope, LocalsDictNodeNG
+from astroid.nodes.scoped_nodes.utils import builtin_lookup
+from astroid.nodes.utils import Position
 
 if sys.version_info >= (3, 6, 2):
     from typing import NoReturn
@@ -90,6 +55,12 @@ if sys.version_info >= (3, 8):
 else:
     from typing_extensions import Literal
 
+if sys.version_info >= (3, 8) or TYPE_CHECKING:
+    from functools import cached_property
+else:
+    # pylint: disable-next=ungrouped-imports
+    from astroid.decorators import cachedproperty as cached_property
+
 
 ITER_METHODS = ("__iter__", "__getitem__")
 EXCEPTION_BASE_CLASSES = frozenset({"Exception", "BaseException"})
@@ -206,175 +177,6 @@ def function_to_method(n, klass):
     return n
 
 
-def builtin_lookup(name):
-    """lookup a name into the builtin module
-    return the list of matching statements and the astroid for the builtin
-    module
-    """
-    builtin_astroid = AstroidManager().ast_from_module(builtins)
-    if name == "__dict__":
-        return builtin_astroid, ()
-    try:
-        stmts = builtin_astroid.locals[name]
-    except KeyError:
-        stmts = ()
-    return builtin_astroid, stmts
-
-
-# TODO move this Mixin to mixins.py; problem: 'FunctionDef' in _scope_lookup
-class LocalsDictNodeNG(node_classes.LookupMixIn, node_classes.NodeNG):
-    """this class provides locals handling common to Module, FunctionDef
-    and ClassDef nodes, including a dict like interface for direct access
-    to locals information
-    """
-
-    # attributes below are set by the builder module or by raw factories
-
-    locals = {}
-    """A map of the name of a local variable to the node defining the local.
-
-    :type: dict(str, NodeNG)
-    """
-
-    def qname(self):
-        """Get the 'qualified' name of the node.
-
-        For example: module.name, module.class.name ...
-
-        :returns: The qualified name.
-        :rtype: str
-        """
-        # pylint: disable=no-member; github.com/pycqa/astroid/issues/278
-        if self.parent is None:
-            return self.name
-        return f"{self.parent.frame(future=True).qname()}.{self.name}"
-
-    def scope(self: T) -> T:
-        """The first parent node defining a new scope.
-
-        :returns: The first parent scope node.
-        :rtype: Module or FunctionDef or ClassDef or Lambda or GenExpr
-        """
-        return self
-
-    def _scope_lookup(self, node, name, offset=0):
-        """XXX method for interfacing the scope lookup"""
-        try:
-            stmts = _filter_stmts(node, self.locals[name], self, offset)
-        except KeyError:
-            stmts = ()
-        if stmts:
-            return self, stmts
-
-        # Handle nested scopes: since class names do not extend to nested
-        # scopes (e.g., methods), we find the next enclosing non-class scope
-        pscope = self.parent and self.parent.scope()
-        while pscope is not None:
-            if not isinstance(pscope, ClassDef):
-                return pscope.scope_lookup(node, name)
-            pscope = pscope.parent and pscope.parent.scope()
-
-        # self is at the top level of a module, or is enclosed only by ClassDefs
-        return builtin_lookup(name)
-
-    def set_local(self, name, stmt):
-        """Define that the given name is declared in the given statement node.
-
-        .. seealso:: :meth:`scope`
-
-        :param name: The name that is being defined.
-        :type name: str
-
-        :param stmt: The statement that defines the given name.
-        :type stmt: NodeNG
-        """
-        # assert not stmt in self.locals.get(name, ()), (self, stmt)
-        self.locals.setdefault(name, []).append(stmt)
-
-    __setitem__ = set_local
-
-    def _append_node(self, child):
-        """append a child, linking it in the tree"""
-        # pylint: disable=no-member; depending by the class
-        # which uses the current class as a mixin or base class.
-        # It's rewritten in 2.0, so it makes no sense for now
-        # to spend development time on it.
-        self.body.append(child)
-        child.parent = self
-
-    def add_local_node(self, child_node, name=None):
-        """Append a child that should alter the locals of this scope node.
-
-        :param child_node: The child node that will alter locals.
-        :type child_node: NodeNG
-
-        :param name: The name of the local that will be altered by
-            the given child node.
-        :type name: str or None
-        """
-        if name != "__class__":
-            # add __class__ node as a child will cause infinite recursion later!
-            self._append_node(child_node)
-        self.set_local(name or child_node.name, child_node)
-
-    def __getitem__(self, item):
-        """The first node the defines the given local.
-
-        :param item: The name of the locally defined object.
-        :type item: str
-
-        :raises KeyError: If the name is not defined.
-        """
-        return self.locals[item][0]
-
-    def __iter__(self):
-        """Iterate over the names of locals defined in this scoped node.
-
-        :returns: The names of the defined locals.
-        :rtype: iterable(str)
-        """
-        return iter(self.keys())
-
-    def keys(self):
-        """The names of locals defined in this scoped node.
-
-        :returns: The names of the defined locals.
-        :rtype: list(str)
-        """
-        return list(self.locals.keys())
-
-    def values(self):
-        """The nodes that define the locals in this scoped node.
-
-        :returns: The nodes that define locals.
-        :rtype: list(NodeNG)
-        """
-        # pylint: disable=consider-using-dict-items
-        # It look like this class override items/keys/values,
-        # probably not worth the headache
-        return [self[key] for key in self.keys()]
-
-    def items(self):
-        """Get the names of the locals and the node that defines the local.
-
-        :returns: The names of locals and their associated node.
-        :rtype: list(tuple(str, NodeNG))
-        """
-        return list(zip(self.keys(), self.values()))
-
-    def __contains__(self, name):
-        """Check if a local is defined in this scope.
-
-        :param name: The name of the local to check for.
-        :type name: str
-
-        :returns: True if this node has a local of the given name,
-            False otherwise.
-        :rtype: bool
-        """
-        return name in self.locals
-
-
 class Module(LocalsDictNodeNG):
     """Class representing an :class:`ast.Module` node.
 
@@ -386,79 +188,32 @@ class Module(LocalsDictNodeNG):
     <Module l.0 at 0x7f23b2e4eda0>
     """
 
-    _astroid_fields = ("body",)
+    _astroid_fields = ("doc_node", "body")
 
-    fromlineno = 0
-    """The first line that this node appears on in the source code.
+    fromlineno: Literal[0] = 0
+    """The first line that this node appears on in the source code."""
 
-    :type: int or None
-    """
     lineno: Literal[0] = 0
-    """The line that this node appears on in the source code.
-    """
+    """The line that this node appears on in the source code."""
 
     # attributes below are set by the builder module or by raw factories
 
-    file = None
-    """The path to the file that this ast has been extracted from.
+    file_bytes: Union[str, bytes, None] = None
+    """The string/bytes that this ast was built from."""
 
-    This will be ``None`` when the representation has been built from a
-    built-in module.
-
-    :type: str or None
-    """
-    file_bytes = None
-    """The string/bytes that this ast was built from.
-
-    :type: str or bytes or None
-    """
-    file_encoding = None
+    file_encoding: Optional[str] = None
     """The encoding of the source file.
 
     This is used to get unicode out of a source file.
     Python 2 only.
-
-    :type: str or None
-    """
-    name = None
-    """The name of the module.
-
-    :type: str or None
     """
-    pure_python = None
-    """Whether the ast was built from source.
 
-    :type: bool or None
-    """
-    package = None
-    """Whether the node represents a package or a module.
-
-    :type: bool or None
-    """
-    globals = None
-    """A map of the name of a global variable to the node defining the global.
-
-    :type: dict(str, NodeNG)
-    """
-
-    # Future imports
-    future_imports = None
-    """The imports from ``__future__``.
-
-    :type: set(str) or None
-    """
     special_attributes = ModuleModel()
-    """The names of special attributes that this module has.
-
-    :type: objectmodel.ModuleModel
-    """
+    """The names of special attributes that this module has."""
 
     # names of module attributes available through the global scope
     scope_attrs = {"__name__", "__doc__", "__file__", "__path__", "__package__"}
-    """The names of module attributes available through the global scope.
-
-    :type: str(str)
-    """
+    """The names of module attributes available through the global scope."""
 
     _other_fields = (
         "name",
@@ -476,67 +231,102 @@ class Module(LocalsDictNodeNG):
     end_col_offset: None
     parent: None
 
+    @decorators_mod.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead")
     def __init__(
         self,
-        name,
-        doc,
-        file=None,
+        name: str,
+        doc: Optional[str] = None,
+        file: Optional[str] = None,
         path: Optional[List[str]] = None,
-        package=None,
-        parent=None,
-        pure_python=True,
-    ):
+        package: Optional[bool] = None,
+        parent: None = None,
+        pure_python: Optional[bool] = True,
+    ) -> None:
         """
         :param name: The name of the module.
-        :type name: str
 
         :param doc: The module docstring.
-        :type doc: str
 
         :param file: The path to the file that this ast has been extracted from.
-        :type file: str or None
 
         :param path:
-        :type path: Optional[List[str]]
 
         :param package: Whether the node represents a package or a module.
-        :type package: bool or None
 
         :param parent: The parent node in the syntax tree.
-        :type parent: NodeNG or None
 
         :param pure_python: Whether the ast was built from source.
-        :type pure_python: bool or None
         """
         self.name = name
-        self.doc = doc
+        """The name of the module."""
+
+        self._doc = doc
+        """The module docstring."""
+
         self.file = file
+        """The path to the file that this ast has been extracted from.
+
+        This will be ``None`` when the representation has been built from a
+        built-in module.
+        """
+
         self.path = path
+
         self.package = package
+        """Whether the node represents a package or a module."""
+
         self.pure_python = pure_python
+        """Whether the ast was built from source."""
+
+        self.globals: Dict[str, List[node_classes.NodeNG]]
+        """A map of the name of a global variable to the node defining the global."""
+
         self.locals = self.globals = {}
-        """A map of the name of a local variable to the node defining the local.
+        """A map of the name of a local variable to the node defining the local."""
 
-        :type: dict(str, NodeNG)
-        """
-        self.body = []
-        """The contents of the module.
+        self.body: Optional[List[node_classes.NodeNG]] = []
+        """The contents of the module."""
 
-        :type: list(NodeNG) or None
-        """
-        self.future_imports = set()
+        self.doc_node: Optional[Const] = None
+        """The doc node associated with this node."""
+
+        self.future_imports: Set[str] = set()
+        """The imports from ``__future__``."""
 
         super().__init__(lineno=0, parent=parent)
 
     # pylint: enable=redefined-builtin
 
-    def postinit(self, body=None):
+    def postinit(self, body=None, *, doc_node: Optional[Const] = None):
         """Do some setup after initialisation.
 
         :param body: The contents of the module.
         :type body: list(NodeNG) or None
+        :param doc_node: The doc node associated with this node.
         """
         self.body = body
+        self.doc_node = doc_node
+        if doc_node:
+            self._doc = doc_node.value
+
+    @property
+    def doc(self) -> Optional[str]:
+        """The module docstring."""
+        warnings.warn(
+            "The 'Module.doc' attribute is deprecated, "
+            "use 'Module.doc_node' instead.",
+            DeprecationWarning,
+        )
+        return self._doc
+
+    @doc.setter
+    def doc(self, value: Optional[str]) -> None:
+        warnings.warn(
+            "Setting the 'Module.doc' attribute is deprecated, "
+            "use 'Module.doc_node' instead.",
+            DeprecationWarning,
+        )
+        self._doc = value
 
     def _get_stream(self):
         if self.file_bytes is not None:
@@ -661,11 +451,9 @@ class Module(LocalsDictNodeNG):
         return self.file is not None and self.file.endswith(".py")
 
     @overload
-    def statement(self, *, future: Literal[None] = ...) -> "Module":
+    def statement(self, *, future: None = ...) -> "Module":
         ...
 
-    # pylint: disable-next=arguments-differ
-    # https://github.com/PyCQA/pylint/issues/5264
     @overload
     def statement(self, *, future: Literal[True]) -> NoReturn:
         ...
@@ -873,12 +661,6 @@ class Module(LocalsDictNodeNG):
         return self
 
 
-class ComprehensionScope(LocalsDictNodeNG):
-    """Scoping for different types of comprehensions."""
-
-    scope_lookup = LocalsDictNodeNG._scope_lookup
-
-
 class GeneratorExp(ComprehensionScope):
     """Class representing an :class:`ast.GeneratorExp` node.
 
@@ -1168,7 +950,7 @@ class SetComp(ComprehensionScope):
         yield from self.generators
 
 
-class _ListComp(node_classes.NodeNG):
+class ListComp(ComprehensionScope):
     """Class representing an :class:`ast.ListComp` node.
 
     >>> import astroid
@@ -1178,17 +960,43 @@ class _ListComp(node_classes.NodeNG):
     """
 
     _astroid_fields = ("elt", "generators")
+    _other_other_fields = ("locals",)
+
     elt = None
     """The element that forms the output of the expression.
 
     :type: NodeNG or None
     """
+
     generators = None
     """The generators that are looped through.
 
     :type: list(Comprehension) or None
     """
 
+    def __init__(
+        self,
+        lineno=None,
+        col_offset=None,
+        parent=None,
+        *,
+        end_lineno=None,
+        end_col_offset=None,
+    ):
+        self.locals = {}
+        """A map of the name of a local variable to the node defining it.
+
+        :type: dict(str, NodeNG)
+        """
+
+        super().__init__(
+            lineno=lineno,
+            col_offset=col_offset,
+            end_lineno=end_lineno,
+            end_col_offset=end_col_offset,
+            parent=parent,
+        )
+
     def postinit(self, elt=None, generators=None):
         """Do some setup after initialisation.
 
@@ -1216,41 +1024,6 @@ class _ListComp(node_classes.NodeNG):
         yield from self.generators
 
 
-class ListComp(_ListComp, ComprehensionScope):
-    """Class representing an :class:`ast.ListComp` node.
-
-    >>> import astroid
-    >>> node = astroid.extract_node('[thing for thing in things if thing]')
-    >>> node
-    <ListComp l.1 at 0x7f23b2e418d0>
-    """
-
-    _other_other_fields = ("locals",)
-
-    def __init__(
-        self,
-        lineno=None,
-        col_offset=None,
-        parent=None,
-        *,
-        end_lineno=None,
-        end_col_offset=None,
-    ):
-        self.locals = {}
-        """A map of the name of a local variable to the node defining it.
-
-        :type: dict(str, NodeNG)
-        """
-
-        super().__init__(
-            lineno=lineno,
-            col_offset=col_offset,
-            end_lineno=end_lineno,
-            end_col_offset=end_col_offset,
-            parent=parent,
-        )
-
-
 def _infer_decorator_callchain(node):
     """Detect decorator call chaining and see if the end result is a
     static or a classmethod.
@@ -1301,6 +1074,8 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
     _other_other_fields = ("locals",)
     name = "<lambda>"
     is_lambda = True
+    special_attributes = FunctionModel()
+    """The names of special attributes that this function has."""
 
     def implicit_parameters(self):
         return 0
@@ -1360,6 +1135,8 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
         :type: list(NodeNG)
         """
 
+        self.instance_attrs: Dict[str, List[NodeNG]] = {}
+
         super().__init__(
             lineno=lineno,
             col_offset=col_offset,
@@ -1409,8 +1186,10 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
         """
         return True
 
-    def argnames(self):
-        """Get the names of each of the arguments.
+    def argnames(self) -> List[str]:
+        """Get the names of each of the arguments, including that
+        of the collections of variable-length arguments ("args", "kwargs",
+        etc.), as well as positional-only and keyword-only arguments.
 
         :returns: The names of the arguments.
         :rtype: list(str)
@@ -1421,6 +1200,7 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
             names = []
         if self.args.vararg:
             names.append(self.args.vararg)
+        names += [elt.name for elt in self.args.kwonlyargs]
         if self.args.kwarg:
             names.append(self.args.kwarg)
         return names
@@ -1487,6 +1267,21 @@ class Lambda(mixins.FilterStmtsMixin, LocalsDictNodeNG):
         """
         return self
 
+    def getattr(
+        self, name: str, context: Optional[InferenceContext] = None
+    ) -> List[NodeNG]:
+        if not name:
+            raise AttributeInferenceError(target=self, attribute=name, context=context)
+
+        found_attrs = []
+        if name in self.instance_attrs:
+            found_attrs = self.instance_attrs[name]
+        if name in self.special_attributes:
+            found_attrs.append(self.special_attributes.lookup(name))
+        if found_attrs:
+            return found_attrs
+        raise AttributeInferenceError(target=self, attribute=name)
+
 
 class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
     """Class representing an :class:`ast.FunctionDef`.
@@ -1500,19 +1295,12 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
     <FunctionDef.my_func l.2 at 0x7f23b2e71e10>
     """
 
-    _astroid_fields = ("decorators", "args", "returns", "body")
+    _astroid_fields = ("decorators", "args", "returns", "doc_node", "body")
     _multi_line_block_fields = ("body",)
     returns = None
-    decorators = None
-    """The decorators that are applied to this method or function.
-
-    :type: Decorators or None
-    """
-    special_attributes = FunctionModel()
-    """The names of special attributes that this function has.
+    decorators: Optional[node_classes.Decorators] = None
+    """The decorators that are applied to this method or function."""
 
-    :type: objectmodel.FunctionModel
-    """
     is_function = True
     """Whether this node indicates a function.
 
@@ -1533,7 +1321,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
     type_comment_returns = None
     """If present, this will contain the return type annotation, passed by a type comment"""
     # attributes below are set by the builder module or by raw factories
-    _other_fields = ("name", "doc")
+    _other_fields = ("name", "doc", "position")
     _other_other_fields = (
         "locals",
         "_type",
@@ -1542,10 +1330,11 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
     )
     _type = None
 
+    @decorators_mod.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead")
     def __init__(
         self,
         name=None,
-        doc=None,
+        doc: Optional[str] = None,
         lineno=None,
         col_offset=None,
         parent=None,
@@ -1557,8 +1346,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
         :param name: The name of the function.
         :type name: str or None
 
-        :param doc: The function's docstring.
-        :type doc: str or None
+        :param doc: The function docstring.
 
         :param lineno: The line that this node appears on in the source code.
         :type lineno: int or None
@@ -1583,11 +1371,11 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
         :type name: str or None
         """
 
-        self.doc = doc
-        """The function's docstring.
+        self._doc = doc
+        """The function docstring."""
 
-        :type doc: str or None
-        """
+        self.doc_node: Optional[Const] = None
+        """The doc node associated with this node."""
 
         self.instance_attrs = {}
         super().__init__(
@@ -1606,10 +1394,13 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
         self,
         args: Arguments,
         body,
-        decorators=None,
+        decorators: Optional[node_classes.Decorators] = None,
         returns=None,
         type_comment_returns=None,
         type_comment_args=None,
+        *,
+        position: Optional[Position] = None,
+        doc_node: Optional[Const] = None,
     ):
         """Do some setup after initialisation.
 
@@ -1625,6 +1416,10 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
             The return type annotation passed via a type comment.
         :params type_comment_args:
             The args type annotation passed via a type comment.
+        :params position:
+            Position of function keyword(s) and name.
+        :param doc_node:
+            The doc node associated with this node.
         """
         self.args = args
         self.body = body
@@ -1632,23 +1427,44 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
         self.returns = returns
         self.type_comment_returns = type_comment_returns
         self.type_comment_args = type_comment_args
+        self.position = position
+        self.doc_node = doc_node
+        if doc_node:
+            self._doc = doc_node.value
 
-    @decorators_mod.cachedproperty
-    def extra_decorators(self):
+    @property
+    def doc(self) -> Optional[str]:
+        """The function docstring."""
+        warnings.warn(
+            "The 'FunctionDef.doc' attribute is deprecated, "
+            "use 'FunctionDef.doc_node' instead.",
+            DeprecationWarning,
+        )
+        return self._doc
+
+    @doc.setter
+    def doc(self, value: Optional[str]) -> None:
+        warnings.warn(
+            "Setting the 'FunctionDef.doc' attribute is deprecated, "
+            "use 'FunctionDef.doc_node' instead.",
+            DeprecationWarning,
+        )
+        self._doc = value
+
+    @cached_property
+    def extra_decorators(self) -> List[node_classes.Call]:
         """The extra decorators that this function can have.
 
         Additional decorators are considered when they are used as
         assignments, as in ``method = staticmethod(method)``.
         The property will return all the callables that are used for
         decoration.
-
-        :type: list(NodeNG)
         """
         frame = self.parent.frame(future=True)
         if not isinstance(frame, ClassDef):
             return []
 
-        decorators = []
+        decorators: List[node_classes.Call] = []
         for assign in frame._get_assign_nodes():
             if isinstance(assign.value, node_classes.Call) and isinstance(
                 assign.value.func, node_classes.Name
@@ -1676,7 +1492,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
                             decorators.append(assign.value)
         return decorators
 
-    @decorators_mod.cachedproperty
+    @cached_property
     def type(
         self,
     ):  # pylint: disable=invalid-overridden-method,too-many-return-statements
@@ -1750,14 +1566,11 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
                 pass
         return type_name
 
-    @decorators_mod.cachedproperty
-    def fromlineno(self):
-        """The first line that this node appears on in the source code.
-
-        :type: int or None
-        """
+    @cached_property
+    def fromlineno(self) -> Optional[int]:
+        """The first line that this node appears on in the source code."""
         # lineno is the line number of the first decorator, we want the def
-        # statement lineno
+        # statement lineno. Similar to 'ClassDef.fromlineno'
         lineno = self.lineno
         if self.decorators is not None:
             lineno += sum(
@@ -1766,7 +1579,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
 
         return lineno
 
-    @decorators_mod.cachedproperty
+    @cached_property
     def blockstart_tolineno(self):
         """The line on which the beginning of this block ends.
 
@@ -1785,22 +1598,6 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda):
         """
         return self.fromlineno, self.tolineno
 
-    def getattr(self, name, context=None):
-        """this method doesn't look in the instance_attrs dictionary since it's
-        done by an Instance proxy at inference time.
-        """
-        if not name:
-            raise AttributeInferenceError(target=self, attribute=name, context=context)
-
-        found_attrs = []
-        if name in self.instance_attrs:
-            found_attrs = self.instance_attrs[name]
-        if name in self.special_attributes:
-            found_attrs.append(self.special_attributes.lookup(name))
-        if found_attrs:
-            return found_attrs
-        raise AttributeInferenceError(target=self, attribute=name)
-
     def igetattr(self, name, context=None):
         """Inferred getattr, which returns an iterator of inferred statements."""
         try:
@@ -2039,7 +1836,7 @@ class AsyncFunctionDef(FunctionDef):
     """
 
 
-def _rec_get_names(args, names=None):
+def _rec_get_names(args, names: Optional[List[str]] = None) -> List[str]:
     """return a list of all argument names"""
     if names is None:
         names = []
@@ -2138,6 +1935,7 @@ def get_wrapping_class(node):
     return klass
 
 
+# pylint: disable=too-many-instance-attributes
 class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement):
     """Class representing an :class:`ast.ClassDef` node.
 
@@ -2155,7 +1953,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
     # by a raw factories
 
     # a dictionary of class instances attributes
-    _astroid_fields = ("decorators", "bases", "keywords", "body")  # name
+    _astroid_fields = ("decorators", "bases", "keywords", "doc_node", "body")  # name
 
     decorators = None
     """The decorators that are applied to this class.
@@ -2179,14 +1977,15 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
             ":type: str"
         ),
     )
-    _other_fields = ("name", "doc")
+    _other_fields = ("name", "doc", "is_dataclass", "position")
     _other_other_fields = ("locals", "_newstyle")
     _newstyle = None
 
+    @decorators_mod.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead")
     def __init__(
         self,
         name=None,
-        doc=None,
+        doc: Optional[str] = None,
         lineno=None,
         col_offset=None,
         parent=None,
@@ -2198,8 +1997,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
         :param name: The name of the class.
         :type name: str or None
 
-        :param doc: The function's docstring.
-        :type doc: str or None
+        :param doc: The class docstring.
 
         :param lineno: The line that this node appears on in the source code.
         :type lineno: int or None
@@ -2251,11 +2049,14 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
         :type name: str or None
         """
 
-        self.doc = doc
-        """The class' docstring.
+        self._doc = doc
+        """The class docstring."""
 
-        :type doc: str or None
-        """
+        self.doc_node: Optional[Const] = None
+        """The doc node associated with this node."""
+
+        self.is_dataclass: bool = False
+        """Whether this class is a dataclass."""
 
         super().__init__(
             lineno=lineno,
@@ -2270,6 +2071,25 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
         for local_name, node in self.implicit_locals():
             self.add_local_node(node, local_name)
 
+    @property
+    def doc(self) -> Optional[str]:
+        """The class docstring."""
+        warnings.warn(
+            "The 'ClassDef.doc' attribute is deprecated, "
+            "use 'ClassDef.doc_node' instead.",
+            DeprecationWarning,
+        )
+        return self._doc
+
+    @doc.setter
+    def doc(self, value: Optional[str]) -> None:
+        warnings.warn(
+            "Setting the 'ClassDef.doc' attribute is deprecated, "
+            "use 'ClassDef.doc_node.value' instead.",
+            DeprecationWarning,
+        )
+        self._doc = value
+
     def implicit_parameters(self):
         return 1
 
@@ -2286,7 +2106,16 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
 
     # pylint: disable=redefined-outer-name
     def postinit(
-        self, bases, body, decorators, newstyle=None, metaclass=None, keywords=None
+        self,
+        bases,
+        body,
+        decorators,
+        newstyle=None,
+        metaclass=None,
+        keywords=None,
+        *,
+        position: Optional[Position] = None,
+        doc_node: Optional[Const] = None,
     ):
         """Do some setup after initialisation.
 
@@ -2307,6 +2136,10 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
 
         :param keywords: The keywords given to the class definition.
         :type keywords: list(Keyword) or None
+
+        :param position: Position of class keyword and name.
+
+        :param doc_node: The doc node associated with this node.
         """
         if keywords is not None:
             self.keywords = keywords
@@ -2317,6 +2150,10 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
             self._newstyle = newstyle
         if metaclass is not None:
             self._metaclass = metaclass
+        self.position = position
+        self.doc_node = doc_node
+        if doc_node:
+            self._doc = doc_node.value
 
     def _newstyle_impl(self, context=None):
         if context is None:
@@ -2342,7 +2179,22 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
         doc=("Whether this is a new style class or not\n\n" ":type: bool or None"),
     )
 
-    @decorators_mod.cachedproperty
+    @cached_property
+    def fromlineno(self) -> Optional[int]:
+        """The first line that this node appears on in the source code."""
+        if not PY38_PLUS or PY38 and IS_PYPY:
+            # For Python < 3.8 the lineno is the line number of the first decorator.
+            # We want the class statement lineno. Similar to 'FunctionDef.fromlineno'
+            lineno = self.lineno
+            if self.decorators is not None:
+                lineno += sum(
+                    node.tolineno - node.lineno + 1 for node in self.decorators.nodes
+                )
+
+            return lineno
+        return super().fromlineno
+
+    @cached_property
     def blockstart_tolineno(self):
         """The line on which the beginning of this block ends.
 
@@ -2419,7 +2271,7 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement
         else:
             return util.Uninferable
 
-        result = ClassDef(name, None)
+        result = ClassDef(name)
 
         # Get the bases of the class.
         try:
diff --git a/astroid/nodes/scoped_nodes/utils.py b/astroid/nodes/scoped_nodes/utils.py
new file mode 100644
index 0000000..272bdad
--- /dev/null
+++ b/astroid/nodes/scoped_nodes/utils.py
@@ -0,0 +1,36 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
+"""
+This module contains utility functions for scoped nodes.
+"""
+
+import builtins
+from typing import TYPE_CHECKING, Sequence, Tuple
+
+from astroid.manager import AstroidManager
+
+if TYPE_CHECKING:
+    from astroid import nodes
+
+
+_builtin_astroid: "nodes.Module | None" = None
+
+
+def builtin_lookup(name: str) -> Tuple["nodes.Module", Sequence["nodes.NodeNG"]]:
+    """Lookup a name in the builtin module.
+
+    Return the list of matching statements and the ast for the builtin module
+    """
+    # pylint: disable-next=global-statement
+    global _builtin_astroid
+    if _builtin_astroid is None:
+        _builtin_astroid = AstroidManager().ast_from_module(builtins)
+    if name == "__dict__":
+        return _builtin_astroid, ()
+    try:
+        stmts: Sequence["nodes.NodeNG"] = _builtin_astroid.locals[name]
+    except KeyError:
+        stmts = ()
+    return _builtin_astroid, stmts
diff --git a/astroid/nodes/utils.py b/astroid/nodes/utils.py
new file mode 100644
index 0000000..5afa718
--- /dev/null
+++ b/astroid/nodes/utils.py
@@ -0,0 +1,14 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
+from typing import NamedTuple
+
+
+class Position(NamedTuple):
+    """Position with line and column information."""
+
+    lineno: int
+    col_offset: int
+    end_lineno: int
+    end_col_offset: int
diff --git a/astroid/objects.py b/astroid/objects.py
index 76ade71..416602e 100644
--- a/astroid/objects.py
+++ b/astroid/objects.py
@@ -1,17 +1,6 @@
-# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2018 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Craig Franklin <craigjfranklin@gmail.com>
-# Copyright (c) 2021 Alphadelta14 <alpha@alphaservcomputing.solutions>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """
 Inference objects are a way to represent composite AST nodes,
@@ -22,6 +11,8 @@ leads to an inferred FrozenSet:
     Call(func=Name('frozenset'), args=Tuple(...))
 """
 
+import sys
+from typing import TYPE_CHECKING
 
 from astroid import bases, decorators, util
 from astroid.exceptions import (
@@ -35,6 +26,11 @@ from astroid.nodes import node_classes, scoped_nodes
 
 objectmodel = util.lazy_import("interpreter.objectmodel")
 
+if sys.version_info >= (3, 8) or TYPE_CHECKING:
+    from functools import cached_property
+else:
+    from astroid.decorators import cachedproperty as cached_property
+
 
 class FrozenSet(node_classes.BaseContainer):
     """class representing a FrozenSet composite node"""
@@ -45,7 +41,7 @@ class FrozenSet(node_classes.BaseContainer):
     def _infer(self, context=None):
         yield self
 
-    @decorators.cachedproperty
+    @cached_property
     def _proxied(self):  # pylint: disable=method-hidden
         ast_builtins = AstroidManager().builtins_module
         return ast_builtins.getattr("frozenset")[0]
@@ -114,7 +110,7 @@ class Super(node_classes.NodeNG):
         index = mro.index(self.mro_pointer)
         return mro[index + 1 :]
 
-    @decorators.cachedproperty
+    @cached_property
     def _proxied(self):
         ast_builtins = AstroidManager().builtins_module
         return ast_builtins.getattr("super")[0]
@@ -218,7 +214,7 @@ class ExceptionInstance(bases.Instance):
     the case of .args.
     """
 
-    @decorators.cachedproperty
+    @cached_property
     def special_attributes(self):
         qname = self.qname()
         instance = objectmodel.BUILTIN_EXCEPTIONS.get(
@@ -259,10 +255,14 @@ class DictValues(bases.Proxy):
 class PartialFunction(scoped_nodes.FunctionDef):
     """A class representing partial function obtained via functools.partial"""
 
+    @decorators.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead")
     def __init__(
         self, call, name=None, doc=None, lineno=None, col_offset=None, parent=None
     ):
-        super().__init__(name, doc, lineno, col_offset, parent=None)
+        # TODO: Pass end_lineno and end_col_offset as well
+        super().__init__(name, lineno=lineno, col_offset=col_offset, parent=None)
+        # Assigned directly to prevent triggering the DeprecationWarning.
+        self._doc = doc
         # 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
@@ -306,11 +306,14 @@ node_classes.Dict.__bases__ = (node_classes.NodeNG, DictInstance)
 class Property(scoped_nodes.FunctionDef):
     """Class representing a Python property"""
 
+    @decorators.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead")
     def __init__(
         self, function, name=None, doc=None, lineno=None, col_offset=None, parent=None
     ):
         self.function = function
-        super().__init__(name, doc, lineno, col_offset, parent)
+        super().__init__(name, lineno=lineno, col_offset=col_offset, parent=parent)
+        # Assigned directly to prevent triggering the DeprecationWarning.
+        self._doc = doc
 
     # pylint: disable=unnecessary-lambda
     special_attributes = util.lazy_descriptor(lambda: objectmodel.PropertyModel())
diff --git a/astroid/protocols.py b/astroid/protocols.py
index 2524fc5..1ec1121 100644
--- a/astroid/protocols.py
+++ b/astroid/protocols.py
@@ -1,30 +1,6 @@
-# Copyright (c) 2009-2011, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2017-2018 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 rr- <rr-@sakuya.pl>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 HoverHell <hoverhell@gmail.com>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Vilnis Termanis <vilnis.termanis@iotics.com>
-# Copyright (c) 2020 Ram Rachum <ram@rachum.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 doranid <ddandd@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """this module contains a set of functions to handle python protocols for nodes
 where it makes sense.
@@ -33,7 +9,6 @@ where it makes sense.
 import collections
 import itertools
 import operator as operator_mod
-import sys
 from typing import Any, Generator, List, Optional, Union
 
 from astroid import arguments, bases, decorators, helpers, nodes, util
@@ -48,11 +23,6 @@ from astroid.exceptions import (
 )
 from astroid.nodes import node_classes
 
-if sys.version_info >= (3, 8):
-    from typing import Literal
-else:
-    from typing_extensions import Literal
-
 raw_building = util.lazy_import("raw_building")
 objects = util.lazy_import("objects")
 
@@ -123,7 +93,7 @@ BIN_OP_IMPL = {
     "/": lambda a, b: a / b,
     "//": lambda a, b: a // b,
     "*": lambda a, b: a * b,
-    "**": lambda a, b: a ** b,
+    "**": lambda a, b: a**b,
     "%": lambda a, b: a % b,
     "&": lambda a, b: a & b,
     "|": lambda a, b: a | b,
@@ -185,7 +155,22 @@ def _filter_uninferable_nodes(elts, context):
 
 
 @decorators.yes_if_nothing_inferred
-def tl_infer_binary_op(self, opnode, operator, other, context, method):
+def tl_infer_binary_op(
+    self,
+    opnode: nodes.BinOp,
+    operator: str,
+    other: nodes.NodeNG,
+    context: InferenceContext,
+    method: nodes.FunctionDef,
+) -> Generator[nodes.NodeNG, None, None]:
+    """Infer a binary operation on a tuple or list.
+
+    The instance on which the binary operation is performed is a tuple
+    or list. This refers to the left-hand side of the operation, so:
+    'tuple() + 1' or '[] + A()'
+    """
+    # For tuples and list the boundnode is no longer the tuple or list instance
+    context.boundnode = None
     not_implemented = nodes.Const(NotImplemented)
     if isinstance(other, self.__class__) and operator == "+":
         node = self.__class__(parent=opnode)
@@ -856,7 +841,7 @@ def match_mapping_assigned_stmts(
     self: nodes.MatchMapping,
     node: nodes.AssignName,
     context: Optional[InferenceContext] = None,
-    assign_path: Literal[None] = None,
+    assign_path: None = None,
 ) -> Generator[nodes.NodeNG, None, None]:
     """Return empty generator (return -> raises StopIteration) so inferred value
     is Uninferable.
@@ -873,7 +858,7 @@ def match_star_assigned_stmts(
     self: nodes.MatchStar,
     node: nodes.AssignName,
     context: Optional[InferenceContext] = None,
-    assign_path: Literal[None] = None,
+    assign_path: None = None,
 ) -> Generator[nodes.NodeNG, None, None]:
     """Return empty generator (return -> raises StopIteration) so inferred value
     is Uninferable.
@@ -890,7 +875,7 @@ def match_as_assigned_stmts(
     self: nodes.MatchAs,
     node: nodes.AssignName,
     context: Optional[InferenceContext] = None,
-    assign_path: Literal[None] = None,
+    assign_path: None = None,
 ) -> Generator[nodes.NodeNG, None, None]:
     """Infer MatchAs as the Match subject if it's the only MatchCase pattern
     else raise StopIteration to yield Uninferable.
diff --git a/astroid/raw_building.py b/astroid/raw_building.py
index 6cd1a79..129a061 100644
--- a/astroid/raw_building.py
+++ b/astroid/raw_building.py
@@ -1,25 +1,6 @@
-# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2015 Ovidiu Sabou <ovidiu@sabou.org>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Becker Awqatty <bawqatty@mide.com>
-# Copyright (c) 2020 Robin Jarry <robin.jarry@6wind.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """this module contains a set of functions to create astroid trees from scratch
 (build_* functions) or from living object (object_build_* functions)
@@ -31,7 +12,7 @@ import os
 import sys
 import types
 import warnings
-from typing import List, Optional
+from typing import Iterable, List, Optional
 
 from astroid import bases, nodes
 from astroid.manager import AstroidManager
@@ -109,19 +90,25 @@ def attach_import_node(node, modname, membername):
 
 def build_module(name: str, doc: Optional[str] = None) -> nodes.Module:
     """create and initialize an astroid Module node"""
-    node = nodes.Module(name, doc, pure_python=False)
-    node.package = False
-    node.parent = None
+    node = nodes.Module(name, pure_python=False, package=False)
+    node.postinit(
+        body=[],
+        doc_node=nodes.Const(value=doc) if doc else None,
+    )
     return node
 
 
-def build_class(name, basenames=(), doc=None):
-    """create and initialize an astroid ClassDef node"""
-    node = nodes.ClassDef(name, doc)
-    for base in basenames:
-        basenode = nodes.Name(name=base)
-        node.bases.append(basenode)
-        basenode.parent = node
+def build_class(
+    name: str, basenames: Iterable[str] = (), doc: Optional[str] = None
+) -> nodes.ClassDef:
+    """Create and initialize an astroid ClassDef node."""
+    node = nodes.ClassDef(name)
+    node.postinit(
+        bases=[nodes.Name(name=base, parent=node) for base in basenames],
+        body=[],
+        decorators=None,
+        doc_node=nodes.Const(value=doc) if doc else None,
+    )
     return node
 
 
@@ -130,13 +117,13 @@ def build_function(
     args: Optional[List[str]] = None,
     posonlyargs: Optional[List[str]] = None,
     defaults=None,
-    doc=None,
+    doc: Optional[str] = None,
     kwonlyargs: Optional[List[str]] = None,
 ) -> nodes.FunctionDef:
     """create and initialize an astroid FunctionDef node"""
     # first argument is now a list of decorators
-    func = nodes.FunctionDef(name, doc)
-    func.args = argsnode = nodes.Arguments(parent=func)
+    func = nodes.FunctionDef(name)
+    argsnode = nodes.Arguments(parent=func)
     argsnode.postinit(
         args=[nodes.AssignName(name=arg, parent=argsnode) for arg in args or ()],
         defaults=[],
@@ -149,6 +136,11 @@ def build_function(
             nodes.AssignName(name=arg, parent=argsnode) for arg in posonlyargs or ()
         ],
     )
+    func.postinit(
+        args=argsnode,
+        body=[],
+        doc_node=nodes.Const(value=doc) if doc else None,
+    )
     for default in defaults or ():
         argsnode.defaults.append(nodes.const_factory(default))
         argsnode.defaults[-1].parent = argsnode
@@ -340,7 +332,7 @@ class InspectBuilder:
         for name in dir(obj):
             try:
                 with warnings.catch_warnings():
-                    warnings.filterwarnings("error")
+                    warnings.simplefilter("error")
                     member = getattr(obj, name)
             except (AttributeError, DeprecationWarning):
                 # damned ExtensionClass.Base, I know you're there !
@@ -462,18 +454,36 @@ def _astroid_bootstrapping():
     # Set the builtin module as parent for some builtins.
     nodes.Const._proxied = property(_set_proxied)
 
-    _GeneratorType = nodes.ClassDef(
-        types.GeneratorType.__name__, types.GeneratorType.__doc__
-    )
+    _GeneratorType = nodes.ClassDef(types.GeneratorType.__name__)
     _GeneratorType.parent = astroid_builtin
+    generator_doc_node = (
+        nodes.Const(value=types.GeneratorType.__doc__)
+        if types.GeneratorType.__doc__
+        else None
+    )
+    _GeneratorType.postinit(
+        bases=[],
+        body=[],
+        decorators=None,
+        doc_node=generator_doc_node,
+    )
     bases.Generator._proxied = _GeneratorType
     builder.object_build(bases.Generator._proxied, types.GeneratorType)
 
     if hasattr(types, "AsyncGeneratorType"):
-        _AsyncGeneratorType = nodes.ClassDef(
-            types.AsyncGeneratorType.__name__, types.AsyncGeneratorType.__doc__
-        )
+        _AsyncGeneratorType = nodes.ClassDef(types.AsyncGeneratorType.__name__)
         _AsyncGeneratorType.parent = astroid_builtin
+        async_generator_doc_node = (
+            nodes.Const(value=types.AsyncGeneratorType.__doc__)
+            if types.AsyncGeneratorType.__doc__
+            else None
+        )
+        _AsyncGeneratorType.postinit(
+            bases=[],
+            body=[],
+            decorators=None,
+            doc_node=async_generator_doc_node,
+        )
         bases.AsyncGenerator._proxied = _AsyncGeneratorType
         builder.object_build(bases.AsyncGenerator._proxied, types.AsyncGeneratorType)
     builtin_types = (
@@ -490,10 +500,16 @@ def _astroid_bootstrapping():
     )
     for _type in builtin_types:
         if _type.__name__ not in astroid_builtin:
-            cls = nodes.ClassDef(_type.__name__, _type.__doc__)
-            cls.parent = astroid_builtin
-            builder.object_build(cls, _type)
-            astroid_builtin[_type.__name__] = cls
+            klass = nodes.ClassDef(_type.__name__)
+            klass.parent = astroid_builtin
+            klass.postinit(
+                bases=[],
+                body=[],
+                decorators=None,
+                doc_node=nodes.Const(value=_type.__doc__) if _type.__doc__ else None,
+            )
+            builder.object_build(klass, _type)
+            astroid_builtin[_type.__name__] = klass
 
 
 _astroid_bootstrapping()
diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py
index d310974..ac8e4a2 100644
--- a/astroid/rebuilder.py
+++ b/astroid/rebuilder.py
@@ -1,36 +1,16 @@
-# Copyright (c) 2009-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014 Alexander Presnyakov <flagist0@gmail.com>
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016-2017 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com>
-# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 rr- <rr-@sakuya.pl>
-# Copyright (c) 2018-2019 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Tomas Gavenciak <gavento@ucw.cz>
-# Copyright (c) 2018 Serhiy Storchaka <storchaka@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019-2021 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2019 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Federico Bond <federicobond@gmail.com>
-# Copyright (c) 2021 hippo91 <guillaume.peillex@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """this module contains utilities for rebuilding an _ast tree in
 order to get a single Astroid representation
 """
 
 import sys
+import token
+import tokenize
+from io import StringIO
+from tokenize import TokenInfo, generate_tokens
 from typing import (
     TYPE_CHECKING,
     Callable,
@@ -38,6 +18,7 @@ from typing import (
     Generator,
     List,
     Optional,
+    Set,
     Tuple,
     Type,
     TypeVar,
@@ -48,9 +29,10 @@ from typing import (
 
 from astroid import nodes
 from astroid._ast import ParserModule, get_parser_module, parse_function_type_comment
-from astroid.const import PY38, PY38_PLUS, Context
+from astroid.const import IS_PYPY, PY36, PY38, PY38_PLUS, PY39_PLUS, Context
 from astroid.manager import AstroidManager
 from astroid.nodes import NodeNG
+from astroid.nodes.utils import Position
 
 if sys.version_info >= (3, 8):
     from typing import Final
@@ -81,6 +63,7 @@ T_Doc = TypeVar(
 T_Function = TypeVar("T_Function", nodes.FunctionDef, nodes.AsyncFunctionDef)
 T_For = TypeVar("T_For", nodes.For, nodes.AsyncFor)
 T_With = TypeVar("T_With", nodes.With, nodes.AsyncWith)
+NodesWithDocsType = Union[nodes.Module, nodes.ClassDef, nodes.FunctionDef]
 
 
 # noinspection PyMethodMayBeStatic
@@ -88,9 +71,13 @@ class TreeRebuilder:
     """Rebuilds the _ast tree to become an Astroid tree"""
 
     def __init__(
-        self, manager: AstroidManager, parser_module: Optional[ParserModule] = None
-    ):
+        self,
+        manager: AstroidManager,
+        parser_module: Optional[ParserModule] = None,
+        data: Optional[str] = None,
+    ) -> None:
         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._delayed_assattr: List[nodes.AssignAttr] = []
@@ -104,7 +91,8 @@ class TreeRebuilder:
             self._parser_module = parser_module
         self._module = self._parser_module.module
 
-    def _get_doc(self, node: T_Doc) -> Tuple[T_Doc, Optional[str]]:
+    def _get_doc(self, node: T_Doc) -> Tuple[T_Doc, Optional["ast.Constant | ast.Str"]]:
+        """Return the doc ast node."""
         try:
             if node.body and isinstance(node.body[0], self._module.Expr):
                 first_value = node.body[0].value
@@ -113,9 +101,13 @@ class TreeRebuilder:
                     and isinstance(first_value, self._module.Constant)
                     and isinstance(first_value.value, str)
                 ):
-                    doc = first_value.value if PY38_PLUS else first_value.s
+                    doc_ast_node = first_value
                     node.body = node.body[1:]
-                    return node, doc
+                    # The ast parser of python < 3.8 sets col_offset of multi-line strings to -1
+                    # as it is unable to determine the value correctly. We reset this to None.
+                    if doc_ast_node.col_offset == -1:
+                        doc_ast_node.col_offset = None
+                    return node, doc_ast_node
         except IndexError:
             pass  # ast built from scratch
         return node, None
@@ -133,6 +125,144 @@ class TreeRebuilder:
     ) -> Context:
         return self._parser_module.context_classes.get(type(node.ctx), Context.Load)
 
+    def _get_position_info(
+        self,
+        node: Union["ast.ClassDef", "ast.FunctionDef", "ast.AsyncFunctionDef"],
+        parent: Union[nodes.ClassDef, nodes.FunctionDef, nodes.AsyncFunctionDef],
+    ) -> Optional[Position]:
+        """Return position information for ClassDef and FunctionDef nodes.
+
+        In contrast to AST positions, these only include the actual keyword(s)
+        and the class / function name.
+
+        >>> @decorator
+        >>> async def some_func(var: int) -> None:
+        >>> ^^^^^^^^^^^^^^^^^^^
+        """
+        if not self._data:
+            return None
+        end_lineno: Optional[int] = getattr(node, "end_lineno", None)
+        if node.body:
+            end_lineno = node.body[0].lineno
+        # pylint: disable-next=unsubscriptable-object
+        data = "\n".join(self._data[node.lineno - 1 : end_lineno])
+
+        start_token: Optional[TokenInfo] = None
+        keyword_tokens: Tuple[int, ...] = (token.NAME,)
+        if isinstance(parent, nodes.AsyncFunctionDef):
+            search_token = "async"
+            if PY36:
+                # In Python 3.6, the token type for 'async' was 'ASYNC'
+                # In Python 3.7, the type was changed to 'NAME' and 'ASYNC' removed
+                # Python 3.8 added it back. However, if we use it unconditionally
+                # we would break 3.7.
+                keyword_tokens = (token.NAME, token.ASYNC)
+        elif isinstance(parent, nodes.FunctionDef):
+            search_token = "def"
+        else:
+            search_token = "class"
+
+        for t in generate_tokens(StringIO(data).readline):
+            if (
+                start_token is not None
+                and t.type == token.NAME
+                and t.string == node.name
+            ):
+                break
+            if t.type in keyword_tokens:
+                if t.string == search_token:
+                    start_token = t
+                    continue
+                if t.string in {"def"}:
+                    continue
+            start_token = None
+        else:
+            return None
+
+        # pylint: disable=undefined-loop-variable
+        return Position(
+            lineno=node.lineno + start_token.start[0] - 1,
+            col_offset=start_token.start[1],
+            end_lineno=node.lineno + t.end[0] - 1,
+            end_col_offset=t.end[1],
+        )
+
+    def _fix_doc_node_position(self, node: NodesWithDocsType) -> None:
+        """Fix start and end position of doc nodes for Python < 3.8."""
+        if not self._data or not node.doc_node or node.lineno is None:
+            return
+        if PY38_PLUS:
+            return
+
+        lineno = node.lineno or 1  # lineno of modules is 0
+        end_range: Optional[int] = node.doc_node.lineno
+        if IS_PYPY and not PY39_PLUS:
+            end_range = None
+        # pylint: disable-next=unsubscriptable-object
+        data = "\n".join(self._data[lineno - 1 : end_range])
+
+        found_start, found_end = False, False
+        open_brackets = 0
+        skip_token: Set[int] = {token.NEWLINE, token.INDENT}
+        if PY36:
+            skip_token.update((tokenize.NL, tokenize.COMMENT))
+        else:
+            # token.NL and token.COMMENT were added in 3.7
+            skip_token.update((token.NL, token.COMMENT))
+
+        if isinstance(node, nodes.Module):
+            found_end = True
+
+        for t in generate_tokens(StringIO(data).readline):
+            if found_end is False:
+                if (
+                    found_start is False
+                    and t.type == token.NAME
+                    and t.string in {"def", "class"}
+                ):
+                    found_start = True
+                elif found_start is True and t.type == token.OP:
+                    if t.exact_type == token.COLON and open_brackets == 0:
+                        found_end = True
+                    elif t.exact_type == token.LPAR:
+                        open_brackets += 1
+                    elif t.exact_type == token.RPAR:
+                        open_brackets -= 1
+                continue
+            if t.type in skip_token:
+                continue
+            if t.type == token.STRING:
+                break
+            return
+        else:
+            return
+
+        # pylint: disable=undefined-loop-variable
+        node.doc_node.lineno = lineno + t.start[0] - 1
+        node.doc_node.col_offset = t.start[1]
+        node.doc_node.end_lineno = lineno + t.end[0] - 1
+        node.doc_node.end_col_offset = t.end[1]
+
+    def _reset_end_lineno(self, newnode: nodes.NodeNG) -> None:
+        """Reset end_lineno and end_col_offset attributes for PyPy 3.8.
+
+        For some nodes, these are either set to -1 or only partially assigned.
+        To keep consistency across astroid and pylint, reset all.
+
+        This has been fixed in PyPy 3.9.
+        For reference, an (incomplete) list of nodes with issues:
+            - ClassDef          - For
+            - FunctionDef       - While
+            - Call              - If
+            - Decorators        - TryExcept
+            - With              - TryFinally
+            - Assign
+        """
+        newnode.end_lineno = None
+        newnode.end_col_offset = None
+        for child_node in newnode.get_children():
+            self._reset_end_lineno(child_node)
+
     def visit_module(
         self, node: "ast.Module", modname: str, modpath: str, package: bool
     ) -> nodes.Module:
@@ -140,661 +270,361 @@ class TreeRebuilder:
 
         Note: Method not called by 'visit'
         """
-        node, doc = self._get_doc(node)
+        node, doc_ast_node = self._get_doc(node)
         newnode = nodes.Module(
             name=modname,
-            doc=doc,
             file=modpath,
             path=[modpath],
             package=package,
             parent=None,
         )
-        newnode.postinit([self.visit(child, newnode) for child in node.body])
+        newnode.postinit(
+            [self.visit(child, newnode) for child in node.body],
+            doc_node=self.visit(doc_ast_node, newnode),
+        )
+        self._fix_doc_node_position(newnode)
+        if IS_PYPY and PY38:
+            self._reset_end_lineno(newnode)
         return newnode
 
-    if sys.version_info >= (3, 10):
-
-        @overload
-        def visit(self, node: "ast.arg", parent: NodeNG) -> nodes.AssignName:
-            ...
-
-        @overload
-        def visit(self, node: "ast.arguments", parent: NodeNG) -> nodes.Arguments:
-            ...
+    @overload
+    def visit(self, node: "ast.arg", parent: NodeNG) -> nodes.AssignName:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Assert", parent: NodeNG) -> nodes.Assert:
-            ...
+    @overload
+    def visit(self, node: "ast.arguments", parent: NodeNG) -> nodes.Arguments:
+        ...
 
-        @overload
-        def visit(
-            self, node: "ast.AsyncFunctionDef", parent: NodeNG
-        ) -> nodes.AsyncFunctionDef:
-            ...
+    @overload
+    def visit(self, node: "ast.Assert", parent: NodeNG) -> nodes.Assert:
+        ...
 
-        @overload
-        def visit(self, node: "ast.AsyncFor", parent: NodeNG) -> nodes.AsyncFor:
-            ...
+    @overload
+    def visit(
+        self, node: "ast.AsyncFunctionDef", parent: NodeNG
+    ) -> nodes.AsyncFunctionDef:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Await", parent: NodeNG) -> nodes.Await:
-            ...
+    @overload
+    def visit(self, node: "ast.AsyncFor", parent: NodeNG) -> nodes.AsyncFor:
+        ...
 
-        @overload
-        def visit(self, node: "ast.AsyncWith", parent: NodeNG) -> nodes.AsyncWith:
-            ...
+    @overload
+    def visit(self, node: "ast.Await", parent: NodeNG) -> nodes.Await:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Assign", parent: NodeNG) -> nodes.Assign:
-            ...
+    @overload
+    def visit(self, node: "ast.AsyncWith", parent: NodeNG) -> nodes.AsyncWith:
+        ...
 
-        @overload
-        def visit(self, node: "ast.AnnAssign", parent: NodeNG) -> nodes.AnnAssign:
-            ...
+    @overload
+    def visit(self, node: "ast.Assign", parent: NodeNG) -> nodes.Assign:
+        ...
 
-        @overload
-        def visit(self, node: "ast.AugAssign", parent: NodeNG) -> nodes.AugAssign:
-            ...
+    @overload
+    def visit(self, node: "ast.AnnAssign", parent: NodeNG) -> nodes.AnnAssign:
+        ...
 
-        @overload
-        def visit(self, node: "ast.BinOp", parent: NodeNG) -> nodes.BinOp:
-            ...
+    @overload
+    def visit(self, node: "ast.AugAssign", parent: NodeNG) -> nodes.AugAssign:
+        ...
 
-        @overload
-        def visit(self, node: "ast.BoolOp", parent: NodeNG) -> nodes.BoolOp:
-            ...
+    @overload
+    def visit(self, node: "ast.BinOp", parent: NodeNG) -> nodes.BinOp:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Break", parent: NodeNG) -> nodes.Break:
-            ...
+    @overload
+    def visit(self, node: "ast.BoolOp", parent: NodeNG) -> nodes.BoolOp:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Call", parent: NodeNG) -> nodes.Call:
-            ...
+    @overload
+    def visit(self, node: "ast.Break", parent: NodeNG) -> nodes.Break:
+        ...
 
-        @overload
-        def visit(self, node: "ast.ClassDef", parent: NodeNG) -> nodes.ClassDef:
-            ...
+    @overload
+    def visit(self, node: "ast.Call", parent: NodeNG) -> nodes.Call:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Continue", parent: NodeNG) -> nodes.Continue:
-            ...
+    @overload
+    def visit(self, node: "ast.ClassDef", parent: NodeNG) -> nodes.ClassDef:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Compare", parent: NodeNG) -> nodes.Compare:
-            ...
+    @overload
+    def visit(self, node: "ast.Continue", parent: NodeNG) -> nodes.Continue:
+        ...
 
-        @overload
-        def visit(
-            self, node: "ast.comprehension", parent: NodeNG
-        ) -> nodes.Comprehension:
-            ...
+    @overload
+    def visit(self, node: "ast.Compare", parent: NodeNG) -> nodes.Compare:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Delete", parent: NodeNG) -> nodes.Delete:
-            ...
+    @overload
+    def visit(self, node: "ast.comprehension", parent: NodeNG) -> nodes.Comprehension:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Dict", parent: NodeNG) -> nodes.Dict:
-            ...
+    @overload
+    def visit(self, node: "ast.Delete", parent: NodeNG) -> nodes.Delete:
+        ...
 
-        @overload
-        def visit(self, node: "ast.DictComp", parent: NodeNG) -> nodes.DictComp:
-            ...
+    @overload
+    def visit(self, node: "ast.Dict", parent: NodeNG) -> nodes.Dict:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Expr", parent: NodeNG) -> nodes.Expr:
-            ...
+    @overload
+    def visit(self, node: "ast.DictComp", parent: NodeNG) -> nodes.DictComp:
+        ...
 
-        # Not used in Python 3.8+
-        @overload
-        def visit(self, node: "ast.Ellipsis", parent: NodeNG) -> nodes.Const:
-            ...
+    @overload
+    def visit(self, node: "ast.Expr", parent: NodeNG) -> nodes.Expr:
+        ...
 
-        @overload
-        def visit(
-            self, node: "ast.ExceptHandler", parent: NodeNG
-        ) -> nodes.ExceptHandler:
-            ...
+    @overload
+    def visit(self, node: "ast.ExceptHandler", parent: NodeNG) -> nodes.ExceptHandler:
+        ...
 
-        # Not used in Python 3.9+
-        @overload
-        def visit(self, node: "ast.ExtSlice", parent: nodes.Subscript) -> nodes.Tuple:
-            ...
+    @overload
+    def visit(self, node: "ast.For", parent: NodeNG) -> nodes.For:
+        ...
 
-        @overload
-        def visit(self, node: "ast.For", parent: NodeNG) -> nodes.For:
-            ...
+    @overload
+    def visit(self, node: "ast.ImportFrom", parent: NodeNG) -> nodes.ImportFrom:
+        ...
 
-        @overload
-        def visit(self, node: "ast.ImportFrom", parent: NodeNG) -> nodes.ImportFrom:
-            ...
+    @overload
+    def visit(self, node: "ast.FunctionDef", parent: NodeNG) -> nodes.FunctionDef:
+        ...
 
-        @overload
-        def visit(self, node: "ast.FunctionDef", parent: NodeNG) -> nodes.FunctionDef:
-            ...
+    @overload
+    def visit(self, node: "ast.GeneratorExp", parent: NodeNG) -> nodes.GeneratorExp:
+        ...
 
-        @overload
-        def visit(self, node: "ast.GeneratorExp", parent: NodeNG) -> nodes.GeneratorExp:
-            ...
+    @overload
+    def visit(self, node: "ast.Attribute", parent: NodeNG) -> nodes.Attribute:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Attribute", parent: NodeNG) -> nodes.Attribute:
-            ...
+    @overload
+    def visit(self, node: "ast.Global", parent: NodeNG) -> nodes.Global:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Global", parent: NodeNG) -> nodes.Global:
-            ...
+    @overload
+    def visit(self, node: "ast.If", parent: NodeNG) -> nodes.If:
+        ...
 
-        @overload
-        def visit(self, node: "ast.If", parent: NodeNG) -> nodes.If:
-            ...
+    @overload
+    def visit(self, node: "ast.IfExp", parent: NodeNG) -> nodes.IfExp:
+        ...
 
-        @overload
-        def visit(self, node: "ast.IfExp", parent: NodeNG) -> nodes.IfExp:
-            ...
+    @overload
+    def visit(self, node: "ast.Import", parent: NodeNG) -> nodes.Import:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Import", parent: NodeNG) -> nodes.Import:
-            ...
+    @overload
+    def visit(self, node: "ast.JoinedStr", parent: NodeNG) -> nodes.JoinedStr:
+        ...
 
-        @overload
-        def visit(self, node: "ast.JoinedStr", parent: NodeNG) -> nodes.JoinedStr:
-            ...
+    @overload
+    def visit(self, node: "ast.FormattedValue", parent: NodeNG) -> nodes.FormattedValue:
+        ...
 
-        @overload
-        def visit(
-            self, node: "ast.FormattedValue", parent: NodeNG
-        ) -> nodes.FormattedValue:
-            ...
+    if sys.version_info >= (3, 8):
 
         @overload
         def visit(self, node: "ast.NamedExpr", parent: NodeNG) -> nodes.NamedExpr:
             ...
 
+    if sys.version_info < (3, 9):
         # Not used in Python 3.9+
         @overload
-        def visit(self, node: "ast.Index", parent: nodes.Subscript) -> NodeNG:
-            ...
-
-        @overload
-        def visit(self, node: "ast.keyword", parent: NodeNG) -> nodes.Keyword:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Lambda", parent: NodeNG) -> nodes.Lambda:
-            ...
-
-        @overload
-        def visit(self, node: "ast.List", parent: NodeNG) -> nodes.List:
-            ...
-
-        @overload
-        def visit(self, node: "ast.ListComp", parent: NodeNG) -> nodes.ListComp:
-            ...
-
-        @overload
-        def visit(
-            self, node: "ast.Name", parent: NodeNG
-        ) -> Union[nodes.Name, nodes.Const, nodes.AssignName, nodes.DelName]:
-            ...
-
-        # Not used in Python 3.8+
-        @overload
-        def visit(self, node: "ast.NameConstant", parent: NodeNG) -> nodes.Const:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Nonlocal", parent: NodeNG) -> nodes.Nonlocal:
-            ...
-
-        # Not used in Python 3.8+
-        @overload
-        def visit(self, node: "ast.Str", parent: NodeNG) -> nodes.Const:
-            ...
-
-        # Not used in Python 3.8+
-        @overload
-        def visit(self, node: "ast.Bytes", parent: NodeNG) -> nodes.Const:
-            ...
-
-        # Not used in Python 3.8+
-        @overload
-        def visit(self, node: "ast.Num", parent: NodeNG) -> nodes.Const:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Constant", parent: NodeNG) -> nodes.Const:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Pass", parent: NodeNG) -> nodes.Pass:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Raise", parent: NodeNG) -> nodes.Raise:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Return", parent: NodeNG) -> nodes.Return:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Set", parent: NodeNG) -> nodes.Set:
-            ...
-
-        @overload
-        def visit(self, node: "ast.SetComp", parent: NodeNG) -> nodes.SetComp:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Slice", parent: nodes.Subscript) -> nodes.Slice:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Subscript", parent: NodeNG) -> nodes.Subscript:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Starred", parent: NodeNG) -> nodes.Starred:
-            ...
-
-        @overload
-        def visit(
-            self, node: "ast.Try", parent: NodeNG
-        ) -> Union[nodes.TryExcept, nodes.TryFinally]:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Tuple", parent: NodeNG) -> nodes.Tuple:
-            ...
-
-        @overload
-        def visit(self, node: "ast.UnaryOp", parent: NodeNG) -> nodes.UnaryOp:
-            ...
-
-        @overload
-        def visit(self, node: "ast.While", parent: NodeNG) -> nodes.While:
-            ...
-
-        @overload
-        def visit(self, node: "ast.With", parent: NodeNG) -> nodes.With:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Yield", parent: NodeNG) -> nodes.Yield:
-            ...
-
-        @overload
-        def visit(self, node: "ast.YieldFrom", parent: NodeNG) -> nodes.YieldFrom:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Match", parent: NodeNG) -> nodes.Match:
-            ...
-
-        @overload
-        def visit(self, node: "ast.match_case", parent: NodeNG) -> nodes.MatchCase:
-            ...
-
-        @overload
-        def visit(self, node: "ast.MatchValue", parent: NodeNG) -> nodes.MatchValue:
-            ...
-
-        @overload
-        def visit(
-            self, node: "ast.MatchSingleton", parent: NodeNG
-        ) -> nodes.MatchSingleton:
-            ...
-
-        @overload
-        def visit(
-            self, node: "ast.MatchSequence", parent: NodeNG
-        ) -> nodes.MatchSequence:
-            ...
-
-        @overload
-        def visit(self, node: "ast.MatchMapping", parent: NodeNG) -> nodes.MatchMapping:
-            ...
-
-        @overload
-        def visit(self, node: "ast.MatchClass", parent: NodeNG) -> nodes.MatchClass:
-            ...
-
-        @overload
-        def visit(self, node: "ast.MatchStar", parent: NodeNG) -> nodes.MatchStar:
-            ...
-
-        @overload
-        def visit(self, node: "ast.MatchAs", parent: NodeNG) -> nodes.MatchAs:
-            ...
-
-        @overload
-        def visit(self, node: "ast.MatchOr", parent: NodeNG) -> nodes.MatchOr:
-            ...
-
-        @overload
-        def visit(self, node: "ast.pattern", parent: NodeNG) -> nodes.Pattern:
-            ...
-
-        @overload
-        def visit(self, node: "ast.AST", parent: NodeNG) -> NodeNG:
-            ...
-
-        @overload
-        def visit(self, node: None, parent: NodeNG) -> None:
-            ...
-
-        def visit(self, node: Optional["ast.AST"], parent: NodeNG) -> Optional[NodeNG]:
-            if node is None:
-                return None
-            cls = node.__class__
-            if cls in self._visit_meths:
-                visit_method = self._visit_meths[cls]
-            else:
-                cls_name = cls.__name__
-                visit_name = "visit_" + REDIRECT.get(cls_name, cls_name).lower()
-                visit_method = getattr(self, visit_name)
-                self._visit_meths[cls] = visit_method
-            return visit_method(node, parent)
-
-    else:
-
-        @overload
-        def visit(self, node: "ast.arg", parent: NodeNG) -> nodes.AssignName:
-            ...
-
-        @overload
-        def visit(self, node: "ast.arguments", parent: NodeNG) -> nodes.Arguments:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Assert", parent: NodeNG) -> nodes.Assert:
-            ...
-
-        @overload
-        def visit(
-            self, node: "ast.AsyncFunctionDef", parent: NodeNG
-        ) -> nodes.AsyncFunctionDef:
-            ...
-
-        @overload
-        def visit(self, node: "ast.AsyncFor", parent: NodeNG) -> nodes.AsyncFor:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Await", parent: NodeNG) -> nodes.Await:
-            ...
-
-        @overload
-        def visit(self, node: "ast.AsyncWith", parent: NodeNG) -> nodes.AsyncWith:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Assign", parent: NodeNG) -> nodes.Assign:
-            ...
-
-        @overload
-        def visit(self, node: "ast.AnnAssign", parent: NodeNG) -> nodes.AnnAssign:
-            ...
-
-        @overload
-        def visit(self, node: "ast.AugAssign", parent: NodeNG) -> nodes.AugAssign:
-            ...
-
-        @overload
-        def visit(self, node: "ast.BinOp", parent: NodeNG) -> nodes.BinOp:
-            ...
-
-        @overload
-        def visit(self, node: "ast.BoolOp", parent: NodeNG) -> nodes.BoolOp:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Break", parent: NodeNG) -> nodes.Break:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Call", parent: NodeNG) -> nodes.Call:
-            ...
-
-        @overload
-        def visit(self, node: "ast.ClassDef", parent: NodeNG) -> nodes.ClassDef:
+        def visit(self, node: "ast.ExtSlice", parent: nodes.Subscript) -> nodes.Tuple:
             ...
 
         @overload
-        def visit(self, node: "ast.Continue", parent: NodeNG) -> nodes.Continue:
+        def visit(self, node: "ast.Index", parent: nodes.Subscript) -> NodeNG:
             ...
 
-        @overload
-        def visit(self, node: "ast.Compare", parent: NodeNG) -> nodes.Compare:
-            ...
+    @overload
+    def visit(self, node: "ast.keyword", parent: NodeNG) -> nodes.Keyword:
+        ...
 
-        @overload
-        def visit(
-            self, node: "ast.comprehension", parent: NodeNG
-        ) -> nodes.Comprehension:
-            ...
+    @overload
+    def visit(self, node: "ast.Lambda", parent: NodeNG) -> nodes.Lambda:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Delete", parent: NodeNG) -> nodes.Delete:
-            ...
+    @overload
+    def visit(self, node: "ast.List", parent: NodeNG) -> nodes.List:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Dict", parent: NodeNG) -> nodes.Dict:
-            ...
+    @overload
+    def visit(self, node: "ast.ListComp", parent: NodeNG) -> nodes.ListComp:
+        ...
 
-        @overload
-        def visit(self, node: "ast.DictComp", parent: NodeNG) -> nodes.DictComp:
-            ...
+    @overload
+    def visit(
+        self, node: "ast.Name", parent: NodeNG
+    ) -> Union[nodes.Name, nodes.Const, nodes.AssignName, nodes.DelName]:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Expr", parent: NodeNG) -> nodes.Expr:
-            ...
+    @overload
+    def visit(self, node: "ast.Nonlocal", parent: NodeNG) -> nodes.Nonlocal:
+        ...
 
+    if sys.version_info < (3, 8):
         # Not used in Python 3.8+
         @overload
         def visit(self, node: "ast.Ellipsis", parent: NodeNG) -> nodes.Const:
             ...
 
         @overload
-        def visit(
-            self, node: "ast.ExceptHandler", parent: NodeNG
-        ) -> nodes.ExceptHandler:
-            ...
-
-        # Not used in Python 3.9+
-        @overload
-        def visit(self, node: "ast.ExtSlice", parent: nodes.Subscript) -> nodes.Tuple:
-            ...
-
-        @overload
-        def visit(self, node: "ast.For", parent: NodeNG) -> nodes.For:
-            ...
-
-        @overload
-        def visit(self, node: "ast.ImportFrom", parent: NodeNG) -> nodes.ImportFrom:
-            ...
-
-        @overload
-        def visit(self, node: "ast.FunctionDef", parent: NodeNG) -> nodes.FunctionDef:
-            ...
-
-        @overload
-        def visit(self, node: "ast.GeneratorExp", parent: NodeNG) -> nodes.GeneratorExp:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Attribute", parent: NodeNG) -> nodes.Attribute:
-            ...
-
-        @overload
-        def visit(self, node: "ast.Global", parent: NodeNG) -> nodes.Global:
-            ...
-
-        @overload
-        def visit(self, node: "ast.If", parent: NodeNG) -> nodes.If:
-            ...
-
-        @overload
-        def visit(self, node: "ast.IfExp", parent: NodeNG) -> nodes.IfExp:
+        def visit(self, node: "ast.NameConstant", parent: NodeNG) -> nodes.Const:
             ...
 
         @overload
-        def visit(self, node: "ast.Import", parent: NodeNG) -> nodes.Import:
+        def visit(self, node: "ast.Str", parent: NodeNG) -> nodes.Const:
             ...
 
         @overload
-        def visit(self, node: "ast.JoinedStr", parent: NodeNG) -> nodes.JoinedStr:
+        def visit(self, node: "ast.Bytes", parent: NodeNG) -> nodes.Const:
             ...
 
         @overload
-        def visit(
-            self, node: "ast.FormattedValue", parent: NodeNG
-        ) -> nodes.FormattedValue:
+        def visit(self, node: "ast.Num", parent: NodeNG) -> nodes.Const:
             ...
 
-        @overload
-        def visit(self, node: "ast.NamedExpr", parent: NodeNG) -> nodes.NamedExpr:
-            ...
+    @overload
+    def visit(self, node: "ast.Constant", parent: NodeNG) -> nodes.Const:
+        ...
 
-        # Not used in Python 3.9+
-        @overload
-        def visit(self, node: "ast.Index", parent: nodes.Subscript) -> NodeNG:
-            ...
+    @overload
+    def visit(self, node: "ast.Pass", parent: NodeNG) -> nodes.Pass:
+        ...
 
-        @overload
-        def visit(self, node: "ast.keyword", parent: NodeNG) -> nodes.Keyword:
-            ...
+    @overload
+    def visit(self, node: "ast.Raise", parent: NodeNG) -> nodes.Raise:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Lambda", parent: NodeNG) -> nodes.Lambda:
-            ...
+    @overload
+    def visit(self, node: "ast.Return", parent: NodeNG) -> nodes.Return:
+        ...
 
-        @overload
-        def visit(self, node: "ast.List", parent: NodeNG) -> nodes.List:
-            ...
+    @overload
+    def visit(self, node: "ast.Set", parent: NodeNG) -> nodes.Set:
+        ...
 
-        @overload
-        def visit(self, node: "ast.ListComp", parent: NodeNG) -> nodes.ListComp:
-            ...
+    @overload
+    def visit(self, node: "ast.SetComp", parent: NodeNG) -> nodes.SetComp:
+        ...
 
-        @overload
-        def visit(
-            self, node: "ast.Name", parent: NodeNG
-        ) -> Union[nodes.Name, nodes.Const, nodes.AssignName, nodes.DelName]:
-            ...
+    @overload
+    def visit(self, node: "ast.Slice", parent: nodes.Subscript) -> nodes.Slice:
+        ...
 
-        # Not used in Python 3.8+
-        @overload
-        def visit(self, node: "ast.NameConstant", parent: NodeNG) -> nodes.Const:
-            ...
+    @overload
+    def visit(self, node: "ast.Subscript", parent: NodeNG) -> nodes.Subscript:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Nonlocal", parent: NodeNG) -> nodes.Nonlocal:
-            ...
+    @overload
+    def visit(self, node: "ast.Starred", parent: NodeNG) -> nodes.Starred:
+        ...
 
-        # Not used in Python 3.8+
-        @overload
-        def visit(self, node: "ast.Str", parent: NodeNG) -> nodes.Const:
-            ...
+    @overload
+    def visit(
+        self, node: "ast.Try", parent: NodeNG
+    ) -> Union[nodes.TryExcept, nodes.TryFinally]:
+        ...
 
-        # Not used in Python 3.8+
-        @overload
-        def visit(self, node: "ast.Bytes", parent: NodeNG) -> nodes.Const:
-            ...
+    @overload
+    def visit(self, node: "ast.Tuple", parent: NodeNG) -> nodes.Tuple:
+        ...
 
-        # Not used in Python 3.8+
-        @overload
-        def visit(self, node: "ast.Num", parent: NodeNG) -> nodes.Const:
-            ...
+    @overload
+    def visit(self, node: "ast.UnaryOp", parent: NodeNG) -> nodes.UnaryOp:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Constant", parent: NodeNG) -> nodes.Const:
-            ...
+    @overload
+    def visit(self, node: "ast.While", parent: NodeNG) -> nodes.While:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Pass", parent: NodeNG) -> nodes.Pass:
-            ...
+    @overload
+    def visit(self, node: "ast.With", parent: NodeNG) -> nodes.With:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Raise", parent: NodeNG) -> nodes.Raise:
-            ...
+    @overload
+    def visit(self, node: "ast.Yield", parent: NodeNG) -> nodes.Yield:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Return", parent: NodeNG) -> nodes.Return:
-            ...
+    @overload
+    def visit(self, node: "ast.YieldFrom", parent: NodeNG) -> nodes.YieldFrom:
+        ...
 
-        @overload
-        def visit(self, node: "ast.Set", parent: NodeNG) -> nodes.Set:
-            ...
+    if sys.version_info >= (3, 10):
 
         @overload
-        def visit(self, node: "ast.SetComp", parent: NodeNG) -> nodes.SetComp:
+        def visit(self, node: "ast.Match", parent: NodeNG) -> nodes.Match:
             ...
 
         @overload
-        def visit(self, node: "ast.Slice", parent: nodes.Subscript) -> nodes.Slice:
+        def visit(self, node: "ast.match_case", parent: NodeNG) -> nodes.MatchCase:
             ...
 
         @overload
-        def visit(self, node: "ast.Subscript", parent: NodeNG) -> nodes.Subscript:
+        def visit(self, node: "ast.MatchValue", parent: NodeNG) -> nodes.MatchValue:
             ...
 
         @overload
-        def visit(self, node: "ast.Starred", parent: NodeNG) -> nodes.Starred:
+        def visit(
+            self, node: "ast.MatchSingleton", parent: NodeNG
+        ) -> nodes.MatchSingleton:
             ...
 
         @overload
         def visit(
-            self, node: "ast.Try", parent: NodeNG
-        ) -> Union[nodes.TryExcept, nodes.TryFinally]:
+            self, node: "ast.MatchSequence", parent: NodeNG
+        ) -> nodes.MatchSequence:
             ...
 
         @overload
-        def visit(self, node: "ast.Tuple", parent: NodeNG) -> nodes.Tuple:
+        def visit(self, node: "ast.MatchMapping", parent: NodeNG) -> nodes.MatchMapping:
             ...
 
         @overload
-        def visit(self, node: "ast.UnaryOp", parent: NodeNG) -> nodes.UnaryOp:
+        def visit(self, node: "ast.MatchClass", parent: NodeNG) -> nodes.MatchClass:
             ...
 
         @overload
-        def visit(self, node: "ast.While", parent: NodeNG) -> nodes.While:
+        def visit(self, node: "ast.MatchStar", parent: NodeNG) -> nodes.MatchStar:
             ...
 
         @overload
-        def visit(self, node: "ast.With", parent: NodeNG) -> nodes.With:
+        def visit(self, node: "ast.MatchAs", parent: NodeNG) -> nodes.MatchAs:
             ...
 
         @overload
-        def visit(self, node: "ast.Yield", parent: NodeNG) -> nodes.Yield:
+        def visit(self, node: "ast.MatchOr", parent: NodeNG) -> nodes.MatchOr:
             ...
 
         @overload
-        def visit(self, node: "ast.YieldFrom", parent: NodeNG) -> nodes.YieldFrom:
+        def visit(self, node: "ast.pattern", parent: NodeNG) -> nodes.Pattern:
             ...
 
-        @overload
-        def visit(self, node: "ast.AST", parent: NodeNG) -> NodeNG:
-            ...
+    @overload
+    def visit(self, node: "ast.AST", parent: NodeNG) -> NodeNG:
+        ...
 
-        @overload
-        def visit(self, node: None, parent: NodeNG) -> None:
-            ...
+    @overload
+    def visit(self, node: None, parent: NodeNG) -> None:
+        ...
 
-        def visit(self, node: Optional["ast.AST"], parent: NodeNG) -> Optional[NodeNG]:
-            if node is None:
-                return None
-            cls = node.__class__
-            if cls in self._visit_meths:
-                visit_method = self._visit_meths[cls]
-            else:
-                cls_name = cls.__name__
-                visit_name = "visit_" + REDIRECT.get(cls_name, cls_name).lower()
-                visit_method = getattr(self, visit_name)
-                self._visit_meths[cls] = visit_method
-            return visit_method(node, parent)
+    def visit(self, node: Optional["ast.AST"], parent: NodeNG) -> Optional[NodeNG]:
+        if node is None:
+            return None
+        cls = node.__class__
+        if cls in self._visit_meths:
+            visit_method = self._visit_meths[cls]
+        else:
+            cls_name = cls.__name__
+            visit_name = "visit_" + REDIRECT.get(cls_name, cls_name).lower()
+            visit_method = getattr(self, visit_name)
+            self._visit_meths[cls] = visit_method
+        return visit_method(node, parent)
 
     def _save_assignment(self, node: Union[nodes.AssignName, nodes.DelName]) -> None:
         """save assignment situation since node.parent is not available yet"""
@@ -888,16 +718,14 @@ class TreeRebuilder:
 
     def visit_assert(self, node: "ast.Assert", parent: NodeNG) -> nodes.Assert:
         """visit a Assert node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Assert(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Assert(node.lineno, node.col_offset, parent)
+        newnode = nodes.Assert(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         msg: Optional[NodeNG] = None
         if node.msg:
             msg = self.visit(node.msg, newnode)
@@ -973,16 +801,14 @@ class TreeRebuilder:
         return self._visit_for(nodes.AsyncFor, node, parent)
 
     def visit_await(self, node: "ast.Await", parent: NodeNG) -> nodes.Await:
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Await(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Await(node.lineno, node.col_offset, parent)
+        newnode = nodes.Await(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(value=self.visit(node.value, newnode))
         return newnode
 
@@ -991,16 +817,14 @@ class TreeRebuilder:
 
     def visit_assign(self, node: "ast.Assign", parent: NodeNG) -> nodes.Assign:
         """visit a Assign node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Assign(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Assign(node.lineno, node.col_offset, parent)
+        newnode = nodes.Assign(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         type_annotation = self.check_type_comment(node, parent=newnode)
         newnode.postinit(
             targets=[self.visit(child, newnode) for child in node.targets],
@@ -1011,16 +835,14 @@ class TreeRebuilder:
 
     def visit_annassign(self, node: "ast.AnnAssign", parent: NodeNG) -> nodes.AnnAssign:
         """visit an AnnAssign node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.AnnAssign(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.AnnAssign(node.lineno, node.col_offset, parent)
+        newnode = nodes.AnnAssign(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             target=self.visit(node.target, newnode),
             annotation=self.visit(node.annotation, newnode),
@@ -1050,43 +872,29 @@ class TreeRebuilder:
         """
         if node_name is None:
             return None
-        if sys.version_info >= (3, 8):
-            newnode = nodes.AssignName(
-                name=node_name,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.AssignName(
-                node_name,
-                node.lineno,
-                node.col_offset,
-                parent,
-            )
+        newnode = nodes.AssignName(
+            name=node_name,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         self._save_assignment(newnode)
         return newnode
 
     def visit_augassign(self, node: "ast.AugAssign", parent: NodeNG) -> nodes.AugAssign:
         """visit a AugAssign node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.AugAssign(
-                op=self._parser_module.bin_op_classes[type(node.op)] + "=",
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.AugAssign(
-                self._parser_module.bin_op_classes[type(node.op)] + "=",
-                node.lineno,
-                node.col_offset,
-                parent,
-            )
+        newnode = nodes.AugAssign(
+            op=self._parser_module.bin_op_classes[type(node.op)] + "=",
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.target, newnode), self.visit(node.value, newnode)
         )
@@ -1094,22 +902,15 @@ class TreeRebuilder:
 
     def visit_binop(self, node: "ast.BinOp", parent: NodeNG) -> nodes.BinOp:
         """visit a BinOp node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.BinOp(
-                op=self._parser_module.bin_op_classes[type(node.op)],
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.BinOp(
-                self._parser_module.bin_op_classes[type(node.op)],
-                node.lineno,
-                node.col_offset,
-                parent,
-            )
+        newnode = nodes.BinOp(
+            op=self._parser_module.bin_op_classes[type(node.op)],
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.left, newnode), self.visit(node.right, newnode)
         )
@@ -1117,49 +918,39 @@ class TreeRebuilder:
 
     def visit_boolop(self, node: "ast.BoolOp", parent: NodeNG) -> nodes.BoolOp:
         """visit a BoolOp node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.BoolOp(
-                op=self._parser_module.bool_op_classes[type(node.op)],
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.BoolOp(
-                self._parser_module.bool_op_classes[type(node.op)],
-                node.lineno,
-                node.col_offset,
-                parent,
-            )
+        newnode = nodes.BoolOp(
+            op=self._parser_module.bool_op_classes[type(node.op)],
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit([self.visit(child, newnode) for child in node.values])
         return newnode
 
     def visit_break(self, node: "ast.Break", parent: NodeNG) -> nodes.Break:
         """visit a Break node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            return nodes.Break(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        return nodes.Break(node.lineno, node.col_offset, parent)
+        return nodes.Break(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
 
     def visit_call(self, node: "ast.Call", parent: NodeNG) -> nodes.Call:
         """visit a CallFunc node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Call(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Call(node.lineno, node.col_offset, parent)
+        newnode = nodes.Call(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             func=self.visit(node.func, newnode),
             args=[self.visit(child, newnode) for child in node.args],
@@ -1171,21 +962,16 @@ class TreeRebuilder:
         self, node: "ast.ClassDef", parent: NodeNG, newstyle: bool = True
     ) -> nodes.ClassDef:
         """visit a ClassDef node to become astroid"""
-        node, doc = self._get_doc(node)
-        if sys.version_info >= (3, 8):
-            newnode = nodes.ClassDef(
-                name=node.name,
-                doc=doc,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.ClassDef(
-                node.name, doc, node.lineno, node.col_offset, parent
-            )
+        node, doc_ast_node = self._get_doc(node)
+        newnode = nodes.ClassDef(
+            name=node.name,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         metaclass = None
         for keyword in node.keywords:
             if keyword.arg == "metaclass":
@@ -1203,33 +989,33 @@ class TreeRebuilder:
                 for kwd in node.keywords
                 if kwd.arg != "metaclass"
             ],
+            position=self._get_position_info(node, newnode),
+            doc_node=self.visit(doc_ast_node, newnode),
         )
+        self._fix_doc_node_position(newnode)
         return newnode
 
     def visit_continue(self, node: "ast.Continue", parent: NodeNG) -> nodes.Continue:
         """visit a Continue node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            return nodes.Continue(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        return nodes.Continue(node.lineno, node.col_offset, parent)
+        return nodes.Continue(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
 
     def visit_compare(self, node: "ast.Compare", parent: NodeNG) -> nodes.Compare:
         """visit a Compare node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Compare(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Compare(node.lineno, node.col_offset, parent)
+        newnode = nodes.Compare(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.left, newnode),
             [
@@ -1289,16 +1075,14 @@ class TreeRebuilder:
 
     def visit_delete(self, node: "ast.Delete", parent: NodeNG) -> nodes.Delete:
         """visit a Delete node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Delete(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Delete(node.lineno, node.col_offset, parent)
+        newnode = nodes.Delete(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit([self.visit(child, newnode) for child in node.targets])
         return newnode
 
@@ -1310,50 +1094,42 @@ class TreeRebuilder:
             rebuilt_value = self.visit(value, newnode)
             if not key:
                 # Extended unpacking
-                if sys.version_info >= (3, 8):
-                    rebuilt_key = nodes.DictUnpack(
-                        lineno=rebuilt_value.lineno,
-                        col_offset=rebuilt_value.col_offset,
-                        end_lineno=rebuilt_value.end_lineno,
-                        end_col_offset=rebuilt_value.end_col_offset,
-                        parent=parent,
-                    )
-                else:
-                    rebuilt_key = nodes.DictUnpack(
-                        rebuilt_value.lineno, rebuilt_value.col_offset, parent
-                    )
+                rebuilt_key = nodes.DictUnpack(
+                    lineno=rebuilt_value.lineno,
+                    col_offset=rebuilt_value.col_offset,
+                    # end_lineno and end_col_offset added in 3.8
+                    end_lineno=getattr(rebuilt_value, "end_lineno", None),
+                    end_col_offset=getattr(rebuilt_value, "end_col_offset", None),
+                    parent=parent,
+                )
             else:
                 rebuilt_key = self.visit(key, newnode)
             yield rebuilt_key, rebuilt_value
 
     def visit_dict(self, node: "ast.Dict", parent: NodeNG) -> nodes.Dict:
         """visit a Dict node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Dict(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Dict(node.lineno, node.col_offset, parent)
+        newnode = nodes.Dict(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         items = list(self._visit_dict_items(node, parent, newnode))
         newnode.postinit(items)
         return newnode
 
     def visit_dictcomp(self, node: "ast.DictComp", parent: NodeNG) -> nodes.DictComp:
         """visit a DictComp node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.DictComp(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.DictComp(node.lineno, node.col_offset, parent)
+        newnode = nodes.DictComp(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.key, newnode),
             self.visit(node.value, newnode),
@@ -1363,43 +1139,29 @@ class TreeRebuilder:
 
     def visit_expr(self, node: "ast.Expr", parent: NodeNG) -> nodes.Expr:
         """visit a Expr node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Expr(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Expr(node.lineno, node.col_offset, parent)
-        newnode.postinit(self.visit(node.value, newnode))
-        return newnode
-
-    # Not used in Python 3.8+.
-    def visit_ellipsis(self, node: "ast.Ellipsis", parent: NodeNG) -> nodes.Const:
-        """visit an Ellipsis node by returning a fresh instance of Const"""
-        return nodes.Const(
-            value=Ellipsis,
+        newnode = nodes.Expr(
             lineno=node.lineno,
             col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
             parent=parent,
         )
+        newnode.postinit(self.visit(node.value, newnode))
+        return newnode
 
     def visit_excepthandler(
         self, node: "ast.ExceptHandler", parent: NodeNG
     ) -> nodes.ExceptHandler:
         """visit an ExceptHandler node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.ExceptHandler(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.ExceptHandler(node.lineno, node.col_offset, parent)
+        newnode = nodes.ExceptHandler(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.type, newnode),
             self.visit_assignname(node, newnode, node.name),
@@ -1407,16 +1169,6 @@ class TreeRebuilder:
         )
         return newnode
 
-    # Not used in Python 3.9+.
-    def visit_extslice(
-        self, node: "ast.ExtSlice", parent: nodes.Subscript
-    ) -> nodes.Tuple:
-        """visit an ExtSlice node by returning a fresh instance of Tuple"""
-        # ExtSlice doesn't have lineno or col_offset information
-        newnode = nodes.Tuple(ctx=Context.Load, parent=parent)
-        newnode.postinit([self.visit(dim, newnode) for dim in node.dims])  # type: ignore[attr-defined]
-        return newnode
-
     @overload
     def _visit_for(
         self, cls: Type[nodes.For], node: "ast.For", parent: NodeNG
@@ -1433,16 +1185,14 @@ class TreeRebuilder:
         self, cls: Type[T_For], node: Union["ast.For", "ast.AsyncFor"], parent: NodeNG
     ) -> T_For:
         """visit a For node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = cls(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = cls(node.lineno, node.col_offset, parent)
+        newnode = cls(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         type_annotation = self.check_type_comment(node, parent=newnode)
         newnode.postinit(
             target=self.visit(node.target, newnode),
@@ -1461,26 +1211,17 @@ class TreeRebuilder:
     ) -> nodes.ImportFrom:
         """visit an ImportFrom node by returning a fresh instance of it"""
         names = [(alias.name, alias.asname) for alias in node.names]
-        if sys.version_info >= (3, 8):
-            newnode = nodes.ImportFrom(
-                fromname=node.module or "",
-                names=names,
-                level=node.level or None,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.ImportFrom(
-                node.module or "",
-                names,
-                node.level or None,
-                node.lineno,
-                node.col_offset,
-                parent,
-            )
+        newnode = nodes.ImportFrom(
+            fromname=node.module or "",
+            names=names,
+            level=node.level or None,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         # store From names to add them to locals after building
         self._import_from_nodes.append(newnode)
         return newnode
@@ -1508,7 +1249,7 @@ class TreeRebuilder:
     ) -> T_Function:
         """visit an FunctionDef node to become astroid"""
         self._global_names.append({})
-        node, doc = self._get_doc(node)
+        node, doc_ast_node = self._get_doc(node)
 
         lineno = node.lineno
         if PY38_PLUS and node.decorator_list:
@@ -1521,18 +1262,15 @@ class TreeRebuilder:
             # the framework for *years*.
             lineno = node.decorator_list[0].lineno
 
-        if sys.version_info >= (3, 8):
-            newnode = cls(
-                name=node.name,
-                doc=doc,
-                lineno=lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = cls(node.name, doc, lineno, node.col_offset, parent)
+        newnode = cls(
+            name=node.name,
+            lineno=lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         decorators = self.visit_decorators(node, newnode)
         returns: Optional[NodeNG]
         if node.returns:
@@ -1551,7 +1289,10 @@ class TreeRebuilder:
             returns=returns,
             type_comment_returns=type_comment_returns,
             type_comment_args=type_comment_args,
+            position=self._get_position_info(node, newnode),
+            doc_node=self.visit(doc_ast_node, newnode),
         )
+        self._fix_doc_node_position(newnode)
         self._global_names.pop()
         return newnode
 
@@ -1564,16 +1305,14 @@ class TreeRebuilder:
         self, node: "ast.GeneratorExp", parent: NodeNG
     ) -> nodes.GeneratorExp:
         """visit a GeneratorExp node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.GeneratorExp(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.GeneratorExp(node.lineno, node.col_offset, parent)
+        newnode = nodes.GeneratorExp(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.elt, newnode),
             [self.visit(child, newnode) for child in node.generators],
@@ -1589,73 +1328,55 @@ class TreeRebuilder:
         if context == Context.Del:
             # FIXME : maybe we should reintroduce and visit_delattr ?
             # for instance, deactivating assign_ctx
-            if sys.version_info >= (3, 8):
-                newnode = nodes.DelAttr(
-                    attrname=node.attr,
-                    lineno=node.lineno,
-                    col_offset=node.col_offset,
-                    end_lineno=node.end_lineno,
-                    end_col_offset=node.end_col_offset,
-                    parent=parent,
-                )
-            else:
-                newnode = nodes.DelAttr(node.attr, node.lineno, node.col_offset, parent)
+            newnode = nodes.DelAttr(
+                attrname=node.attr,
+                lineno=node.lineno,
+                col_offset=node.col_offset,
+                # end_lineno and end_col_offset added in 3.8
+                end_lineno=getattr(node, "end_lineno", None),
+                end_col_offset=getattr(node, "end_col_offset", None),
+                parent=parent,
+            )
         elif context == Context.Store:
-            if sys.version_info >= (3, 8):
-                newnode = nodes.AssignAttr(
-                    attrname=node.attr,
-                    lineno=node.lineno,
-                    col_offset=node.col_offset,
-                    end_lineno=node.end_lineno,
-                    end_col_offset=node.end_col_offset,
-                    parent=parent,
-                )
-            else:
-                newnode = nodes.AssignAttr(
-                    node.attr, node.lineno, node.col_offset, parent
-                )
+            # pylint: disable=redefined-variable-type
+            newnode = nodes.AssignAttr(
+                attrname=node.attr,
+                lineno=node.lineno,
+                col_offset=node.col_offset,
+                # end_lineno and end_col_offset added in 3.8
+                end_lineno=getattr(node, "end_lineno", None),
+                end_col_offset=getattr(node, "end_col_offset", None),
+                parent=parent,
+            )
             # Prohibit a local save if we are in an ExceptHandler.
             if not isinstance(parent, nodes.ExceptHandler):
                 # mypy doesn't recognize that newnode has to be AssignAttr because it doesn't support ParamSpec
                 # See https://github.com/python/mypy/issues/8645
                 self._delayed_assattr.append(newnode)  # type: ignore[arg-type]
         else:
-            # pylint: disable-next=else-if-used
-            # Preserve symmetry with other cases
-            if sys.version_info >= (3, 8):
-                newnode = nodes.Attribute(
-                    attrname=node.attr,
-                    lineno=node.lineno,
-                    col_offset=node.col_offset,
-                    end_lineno=node.end_lineno,
-                    end_col_offset=node.end_col_offset,
-                    parent=parent,
-                )
-            else:
-                newnode = nodes.Attribute(
-                    node.attr, node.lineno, node.col_offset, parent
-                )
+            newnode = nodes.Attribute(
+                attrname=node.attr,
+                lineno=node.lineno,
+                col_offset=node.col_offset,
+                # end_lineno and end_col_offset added in 3.8
+                end_lineno=getattr(node, "end_lineno", None),
+                end_col_offset=getattr(node, "end_col_offset", None),
+                parent=parent,
+            )
         newnode.postinit(self.visit(node.value, newnode))
         return newnode
 
     def visit_global(self, node: "ast.Global", parent: NodeNG) -> nodes.Global:
         """visit a Global node to become astroid"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Global(
-                names=node.names,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Global(
-                node.names,
-                node.lineno,
-                node.col_offset,
-                parent,
-            )
+        newnode = nodes.Global(
+            names=node.names,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         if self._global_names:  # global at the module level, no effect
             for name in node.names:
                 self._global_names[-1].setdefault(name, []).append(newnode)
@@ -1663,16 +1384,14 @@ class TreeRebuilder:
 
     def visit_if(self, node: "ast.If", parent: NodeNG) -> nodes.If:
         """visit an If node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.If(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.If(node.lineno, node.col_offset, parent)
+        newnode = nodes.If(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.test, newnode),
             [self.visit(child, newnode) for child in node.body],
@@ -1682,16 +1401,14 @@ class TreeRebuilder:
 
     def visit_ifexp(self, node: "ast.IfExp", parent: NodeNG) -> nodes.IfExp:
         """visit a IfExp node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.IfExp(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.IfExp(node.lineno, node.col_offset, parent)
+        newnode = nodes.IfExp(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.test, newnode),
             self.visit(node.body, newnode),
@@ -1702,22 +1419,15 @@ class TreeRebuilder:
     def visit_import(self, node: "ast.Import", parent: NodeNG) -> nodes.Import:
         """visit a Import node by returning a fresh instance of it"""
         names = [(alias.name, alias.asname) for alias in node.names]
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Import(
-                names=names,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Import(
-                names,
-                node.lineno,
-                node.col_offset,
-                parent,
-            )
+        newnode = nodes.Import(
+            names=names,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         # save import names in parent's locals:
         for (name, asname) in newnode.names:
             name = asname or name
@@ -1725,32 +1435,28 @@ class TreeRebuilder:
         return newnode
 
     def visit_joinedstr(self, node: "ast.JoinedStr", parent: NodeNG) -> nodes.JoinedStr:
-        if sys.version_info >= (3, 8):
-            newnode = nodes.JoinedStr(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.JoinedStr(node.lineno, node.col_offset, parent)
+        newnode = nodes.JoinedStr(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit([self.visit(child, newnode) for child in node.values])
         return newnode
 
     def visit_formattedvalue(
         self, node: "ast.FormattedValue", parent: NodeNG
     ) -> nodes.FormattedValue:
-        if sys.version_info >= (3, 8):
-            newnode = nodes.FormattedValue(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.FormattedValue(node.lineno, node.col_offset, parent)
+        newnode = nodes.FormattedValue(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.value, newnode),
             node.conversion,
@@ -1758,92 +1464,91 @@ class TreeRebuilder:
         )
         return newnode
 
-    def visit_namedexpr(self, node: "ast.NamedExpr", parent: NodeNG) -> nodes.NamedExpr:
-        if sys.version_info >= (3, 8):
+    if sys.version_info >= (3, 8):
+
+        def visit_namedexpr(
+            self, node: "ast.NamedExpr", parent: NodeNG
+        ) -> nodes.NamedExpr:
             newnode = nodes.NamedExpr(
                 lineno=node.lineno,
                 col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
+                # end_lineno and end_col_offset added in 3.8
+                end_lineno=getattr(node, "end_lineno", None),
+                end_col_offset=getattr(node, "end_col_offset", None),
                 parent=parent,
             )
-        else:
-            newnode = nodes.NamedExpr(node.lineno, node.col_offset, parent)
-        newnode.postinit(
-            self.visit(node.target, newnode), self.visit(node.value, newnode)
-        )
-        return newnode
+            newnode.postinit(
+                self.visit(node.target, newnode), self.visit(node.value, newnode)
+            )
+            return newnode
+
+    if sys.version_info < (3, 9):
+        # Not used in Python 3.9+.
+        def visit_extslice(
+            self, node: "ast.ExtSlice", parent: nodes.Subscript
+        ) -> nodes.Tuple:
+            """visit an ExtSlice node by returning a fresh instance of Tuple"""
+            # ExtSlice doesn't have lineno or col_offset information
+            newnode = nodes.Tuple(ctx=Context.Load, parent=parent)
+            newnode.postinit([self.visit(dim, newnode) for dim in node.dims])
+            return newnode
 
-    # Not used in Python 3.9+.
-    def visit_index(self, node: "ast.Index", parent: nodes.Subscript) -> NodeNG:
-        """visit a Index node by returning a fresh instance of NodeNG"""
-        return self.visit(node.value, parent)  # type: ignore[attr-defined]
+        def visit_index(self, node: "ast.Index", parent: nodes.Subscript) -> NodeNG:
+            """visit a Index node by returning a fresh instance of NodeNG"""
+            return self.visit(node.value, parent)
 
     def visit_keyword(self, node: "ast.keyword", parent: NodeNG) -> nodes.Keyword:
         """visit a Keyword node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 9):
-            newnode = nodes.Keyword(
-                arg=node.arg,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Keyword(node.arg, parent=parent)
+        newnode = nodes.Keyword(
+            arg=node.arg,
+            # position attributes added in 3.9
+            lineno=getattr(node, "lineno", None),
+            col_offset=getattr(node, "col_offset", None),
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(self.visit(node.value, newnode))
         return newnode
 
     def visit_lambda(self, node: "ast.Lambda", parent: NodeNG) -> nodes.Lambda:
         """visit a Lambda node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Lambda(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Lambda(node.lineno, node.col_offset, parent)
+        newnode = nodes.Lambda(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(self.visit(node.args, newnode), self.visit(node.body, newnode))
         return newnode
 
     def visit_list(self, node: "ast.List", parent: NodeNG) -> nodes.List:
         """visit a List node by returning a fresh instance of it"""
         context = self._get_context(node)
-        if sys.version_info >= (3, 8):
-            newnode = nodes.List(
-                ctx=context,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.List(
-                ctx=context,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                parent=parent,
-            )
+        newnode = nodes.List(
+            ctx=context,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit([self.visit(child, newnode) for child in node.elts])
         return newnode
 
     def visit_listcomp(self, node: "ast.ListComp", parent: NodeNG) -> nodes.ListComp:
         """visit a ListComp node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.ListComp(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.ListComp(node.lineno, node.col_offset, parent)
+        newnode = nodes.ListComp(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.elt, newnode),
             [self.visit(child, newnode) for child in node.generators],
@@ -1857,150 +1562,132 @@ class TreeRebuilder:
         context = self._get_context(node)
         newnode: Union[nodes.Name, nodes.AssignName, nodes.DelName]
         if context == Context.Del:
-            if sys.version_info >= (3, 8):
-                newnode = nodes.DelName(
-                    name=node.id,
-                    lineno=node.lineno,
-                    col_offset=node.col_offset,
-                    end_lineno=node.end_lineno,
-                    end_col_offset=node.end_col_offset,
-                    parent=parent,
-                )
-            else:
-                newnode = nodes.DelName(node.id, node.lineno, node.col_offset, parent)
+            newnode = nodes.DelName(
+                name=node.id,
+                lineno=node.lineno,
+                col_offset=node.col_offset,
+                # end_lineno and end_col_offset added in 3.8
+                end_lineno=getattr(node, "end_lineno", None),
+                end_col_offset=getattr(node, "end_col_offset", None),
+                parent=parent,
+            )
         elif context == Context.Store:
-            if sys.version_info >= (3, 8):
-                newnode = nodes.AssignName(
-                    name=node.id,
-                    lineno=node.lineno,
-                    col_offset=node.col_offset,
-                    end_lineno=node.end_lineno,
-                    end_col_offset=node.end_col_offset,
-                    parent=parent,
-                )
-            else:
-                newnode = nodes.AssignName(
-                    node.id, node.lineno, node.col_offset, parent
-                )
+            # pylint: disable=redefined-variable-type
+            newnode = nodes.AssignName(
+                name=node.id,
+                lineno=node.lineno,
+                col_offset=node.col_offset,
+                # end_lineno and end_col_offset added in 3.8
+                end_lineno=getattr(node, "end_lineno", None),
+                end_col_offset=getattr(node, "end_col_offset", None),
+                parent=parent,
+            )
         else:
-            # pylint: disable-next=else-if-used
-            # Preserve symmetry with other cases
-            if sys.version_info >= (3, 8):
-                newnode = nodes.Name(
-                    name=node.id,
-                    lineno=node.lineno,
-                    col_offset=node.col_offset,
-                    end_lineno=node.end_lineno,
-                    end_col_offset=node.end_col_offset,
-                    parent=parent,
-                )
-            else:
-                newnode = nodes.Name(node.id, node.lineno, node.col_offset, parent)
+            newnode = nodes.Name(
+                name=node.id,
+                lineno=node.lineno,
+                col_offset=node.col_offset,
+                # end_lineno and end_col_offset added in 3.8
+                end_lineno=getattr(node, "end_lineno", None),
+                end_col_offset=getattr(node, "end_col_offset", None),
+                parent=parent,
+            )
         # XXX REMOVE me :
         if context in (Context.Del, Context.Store):  # 'Aug' ??
             newnode = cast(Union[nodes.AssignName, nodes.DelName], newnode)
             self._save_assignment(newnode)
         return newnode
 
-    # Not used in Python 3.8+.
-    def visit_nameconstant(
-        self, node: "ast.NameConstant", parent: NodeNG
-    ) -> nodes.Const:
-        # For singleton values True / False / None
-        return nodes.Const(
-            node.value,
-            node.lineno,
-            node.col_offset,
-            parent,
-        )
-
     def visit_nonlocal(self, node: "ast.Nonlocal", parent: NodeNG) -> nodes.Nonlocal:
         """visit a Nonlocal node and return a new instance of it"""
-        if sys.version_info >= (3, 8):
-            return nodes.Nonlocal(
-                names=node.names,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
         return nodes.Nonlocal(
-            node.names,
-            node.lineno,
-            node.col_offset,
-            parent,
+            names=node.names,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
         )
 
     def visit_constant(self, node: "ast.Constant", parent: NodeNG) -> nodes.Const:
         """visit a Constant node by returning a fresh instance of Const"""
-        if sys.version_info >= (3, 8):
+        return nodes.Const(
+            value=node.value,
+            kind=node.kind,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
+
+    if sys.version_info < (3, 8):
+        # Not used in Python 3.8+.
+        def visit_ellipsis(self, node: "ast.Ellipsis", parent: NodeNG) -> nodes.Const:
+            """visit an Ellipsis node by returning a fresh instance of Const"""
             return nodes.Const(
-                value=node.value,
-                kind=node.kind,
+                value=Ellipsis,
                 lineno=node.lineno,
                 col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
                 parent=parent,
             )
-        return nodes.Const(
-            node.value,
-            node.lineno,
-            node.col_offset,
-            parent,
-            node.kind,
-        )
 
-    # Not used in Python 3.8+.
-    def visit_str(
-        self, node: Union["ast.Str", "ast.Bytes"], parent: NodeNG
-    ) -> nodes.Const:
-        """visit a String/Bytes node by returning a fresh instance of Const"""
-        return nodes.Const(
-            node.s,
-            node.lineno,
-            node.col_offset,
-            parent,
-        )
+        def visit_nameconstant(
+            self, node: "ast.NameConstant", parent: NodeNG
+        ) -> nodes.Const:
+            # For singleton values True / False / None
+            return nodes.Const(
+                node.value,
+                node.lineno,
+                node.col_offset,
+                parent,
+            )
+
+        def visit_str(
+            self, node: Union["ast.Str", "ast.Bytes"], parent: NodeNG
+        ) -> nodes.Const:
+            """visit a String/Bytes node by returning a fresh instance of Const"""
+            return nodes.Const(
+                node.s,
+                node.lineno,
+                node.col_offset,
+                parent,
+            )
 
-    # Not used in Python 3.8+
-    visit_bytes = visit_str
+        visit_bytes = visit_str
 
-    # Not used in Python 3.8+.
-    def visit_num(self, node: "ast.Num", parent: NodeNG) -> nodes.Const:
-        """visit a Num node by returning a fresh instance of Const"""
-        return nodes.Const(
-            node.n,
-            node.lineno,
-            node.col_offset,
-            parent,
-        )
+        def visit_num(self, node: "ast.Num", parent: NodeNG) -> nodes.Const:
+            """visit a Num node by returning a fresh instance of Const"""
+            return nodes.Const(
+                node.n,
+                node.lineno,
+                node.col_offset,
+                parent,
+            )
 
     def visit_pass(self, node: "ast.Pass", parent: NodeNG) -> nodes.Pass:
         """visit a Pass node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            return nodes.Pass(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        return nodes.Pass(node.lineno, node.col_offset, parent)
+        return nodes.Pass(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
 
     def visit_raise(self, node: "ast.Raise", parent: NodeNG) -> nodes.Raise:
         """visit a Raise node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Raise(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Raise(node.lineno, node.col_offset, parent)
+        newnode = nodes.Raise(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         # no traceback; anyway it is not used in Pylint
         newnode.postinit(
             exc=self.visit(node.exc, newnode),
@@ -2010,47 +1697,41 @@ class TreeRebuilder:
 
     def visit_return(self, node: "ast.Return", parent: NodeNG) -> nodes.Return:
         """visit a Return node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Return(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Return(node.lineno, node.col_offset, parent)
+        newnode = nodes.Return(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         if node.value is not None:
             newnode.postinit(self.visit(node.value, newnode))
         return newnode
 
     def visit_set(self, node: "ast.Set", parent: NodeNG) -> nodes.Set:
         """visit a Set node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Set(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Set(node.lineno, node.col_offset, parent)
+        newnode = nodes.Set(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit([self.visit(child, newnode) for child in node.elts])
         return newnode
 
     def visit_setcomp(self, node: "ast.SetComp", parent: NodeNG) -> nodes.SetComp:
         """visit a SetComp node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.SetComp(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.SetComp(node.lineno, node.col_offset, parent)
+        newnode = nodes.SetComp(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.elt, newnode),
             [self.visit(child, newnode) for child in node.generators],
@@ -2059,16 +1740,14 @@ class TreeRebuilder:
 
     def visit_slice(self, node: "ast.Slice", parent: nodes.Subscript) -> nodes.Slice:
         """visit a Slice node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 9):
-            newnode = nodes.Slice(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Slice(parent=parent)
+        newnode = nodes.Slice(
+            # position attributes added in 3.9
+            lineno=getattr(node, "lineno", None),
+            col_offset=getattr(node, "col_offset", None),
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             lower=self.visit(node.lower, newnode),
             upper=self.visit(node.upper, newnode),
@@ -2079,22 +1758,15 @@ class TreeRebuilder:
     def visit_subscript(self, node: "ast.Subscript", parent: NodeNG) -> nodes.Subscript:
         """visit a Subscript node by returning a fresh instance of it"""
         context = self._get_context(node)
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Subscript(
-                ctx=context,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Subscript(
-                ctx=context,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                parent=parent,
-            )
+        newnode = nodes.Subscript(
+            ctx=context,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.value, newnode), self.visit(node.slice, newnode)
         )
@@ -2103,33 +1775,36 @@ class TreeRebuilder:
     def visit_starred(self, node: "ast.Starred", parent: NodeNG) -> nodes.Starred:
         """visit a Starred node and return a new instance of it"""
         context = self._get_context(node)
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Starred(
-                ctx=context,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Starred(
-                ctx=context,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                parent=parent,
-            )
+        newnode = nodes.Starred(
+            ctx=context,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(self.visit(node.value, newnode))
         return newnode
 
     def visit_tryexcept(self, node: "ast.Try", parent: NodeNG) -> nodes.TryExcept:
         """visit a TryExcept node by returning a fresh instance of it"""
         if sys.version_info >= (3, 8):
+            # TryExcept excludes the 'finally' but that will be included in the
+            # end_lineno from 'node'. Therefore, we check all non 'finally'
+            # children to find the correct end_lineno and column.
+            end_lineno = node.end_lineno
+            end_col_offset = node.end_col_offset
+            all_children: List["ast.AST"] = [*node.body, *node.handlers, *node.orelse]
+            for child in reversed(all_children):
+                end_lineno = child.end_lineno
+                end_col_offset = child.end_col_offset
+                break
             newnode = nodes.TryExcept(
                 lineno=node.lineno,
                 col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
+                end_lineno=end_lineno,
+                end_col_offset=end_col_offset,
                 parent=parent,
             )
         else:
@@ -2147,17 +1822,15 @@ class TreeRebuilder:
         # python 3.3 introduce a new Try node replacing
         # TryFinally/TryExcept nodes
         if node.finalbody:
-            if sys.version_info >= (3, 8):
-                newnode = nodes.TryFinally(
-                    lineno=node.lineno,
-                    col_offset=node.col_offset,
-                    end_lineno=node.end_lineno,
-                    end_col_offset=node.end_col_offset,
-                    parent=parent,
-                )
-            else:
-                newnode = nodes.TryFinally(node.lineno, node.col_offset, parent)
-            body: Union[List[nodes.TryExcept], List[NodeNG]]
+            newnode = nodes.TryFinally(
+                lineno=node.lineno,
+                col_offset=node.col_offset,
+                # end_lineno and end_col_offset added in 3.8
+                end_lineno=getattr(node, "end_lineno", None),
+                end_col_offset=getattr(node, "end_col_offset", None),
+                parent=parent,
+            )
+            body: List[Union[NodeNG, nodes.TryExcept]]
             if node.handlers:
                 body = [self.visit_tryexcept(node, newnode)]
             else:
@@ -2168,79 +1841,45 @@ class TreeRebuilder:
             return self.visit_tryexcept(node, parent)
         return None
 
-    def visit_tryfinally(self, node: "ast.Try", parent: NodeNG) -> nodes.TryFinally:
-        """visit a TryFinally node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.TryFinally(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.TryFinally(node.lineno, node.col_offset, parent)
-        newnode.postinit(
-            [self.visit(child, newnode) for child in node.body],
-            [self.visit(n, newnode) for n in node.finalbody],
-        )
-        return newnode
-
     def visit_tuple(self, node: "ast.Tuple", parent: NodeNG) -> nodes.Tuple:
         """visit a Tuple node by returning a fresh instance of it"""
         context = self._get_context(node)
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Tuple(
-                ctx=context,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Tuple(
-                ctx=context,
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                parent=parent,
-            )
+        newnode = nodes.Tuple(
+            ctx=context,
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit([self.visit(child, newnode) for child in node.elts])
         return newnode
 
     def visit_unaryop(self, node: "ast.UnaryOp", parent: NodeNG) -> nodes.UnaryOp:
         """visit a UnaryOp node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.UnaryOp(
-                op=self._parser_module.unary_op_classes[node.op.__class__],
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.UnaryOp(
-                self._parser_module.unary_op_classes[node.op.__class__],
-                node.lineno,
-                node.col_offset,
-                parent,
-            )
+        newnode = nodes.UnaryOp(
+            op=self._parser_module.unary_op_classes[node.op.__class__],
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(self.visit(node.operand, newnode))
         return newnode
 
     def visit_while(self, node: "ast.While", parent: NodeNG) -> nodes.While:
         """visit a While node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.While(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.While(node.lineno, node.col_offset, parent)
+        newnode = nodes.While(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         newnode.postinit(
             self.visit(node.test, newnode),
             [self.visit(child, newnode) for child in node.body],
@@ -2266,16 +1905,14 @@ class TreeRebuilder:
         node: Union["ast.With", "ast.AsyncWith"],
         parent: NodeNG,
     ) -> T_With:
-        if sys.version_info >= (3, 8):
-            newnode = cls(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = cls(node.lineno, node.col_offset, parent)
+        newnode = cls(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
 
         def visit_child(child: "ast.withitem") -> Tuple[NodeNG, Optional[NodeNG]]:
             expr = self.visit(child.context_expr, newnode)
@@ -2295,31 +1932,27 @@ class TreeRebuilder:
 
     def visit_yield(self, node: "ast.Yield", parent: NodeNG) -> NodeNG:
         """visit a Yield node by returning a fresh instance of it"""
-        if sys.version_info >= (3, 8):
-            newnode = nodes.Yield(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.Yield(node.lineno, node.col_offset, parent)
+        newnode = nodes.Yield(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         if node.value is not None:
             newnode.postinit(self.visit(node.value, newnode))
         return newnode
 
     def visit_yieldfrom(self, node: "ast.YieldFrom", parent: NodeNG) -> NodeNG:
-        if sys.version_info >= (3, 8):
-            newnode = nodes.YieldFrom(
-                lineno=node.lineno,
-                col_offset=node.col_offset,
-                end_lineno=node.end_lineno,
-                end_col_offset=node.end_col_offset,
-                parent=parent,
-            )
-        else:
-            newnode = nodes.YieldFrom(node.lineno, node.col_offset, parent)
+        newnode = nodes.YieldFrom(
+            lineno=node.lineno,
+            col_offset=node.col_offset,
+            # end_lineno and end_col_offset added in 3.8
+            end_lineno=getattr(node, "end_lineno", None),
+            end_col_offset=getattr(node, "end_col_offset", None),
+            parent=parent,
+        )
         if node.value is not None:
             newnode.postinit(self.visit(node.value, newnode))
         return newnode
@@ -2368,7 +2001,7 @@ class TreeRebuilder:
             self, node: "ast.MatchSingleton", parent: NodeNG
         ) -> nodes.MatchSingleton:
             return nodes.MatchSingleton(
-                value=node.value,  # type: ignore[arg-type] # See https://github.com/python/mypy/pull/10389
+                value=node.value,
                 lineno=node.lineno,
                 col_offset=node.col_offset,
                 end_lineno=node.end_lineno,
diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py
index 8d33590..677f892 100644
--- a/astroid/scoped_nodes.py
+++ b/astroid/scoped_nodes.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 # pylint: disable=unused-import
 
 import warnings
diff --git a/astroid/test_utils.py b/astroid/test_utils.py
index 7450a1f..2ab7383 100644
--- a/astroid/test_utils.py
+++ b/astroid/test_utils.py
@@ -1,16 +1,6 @@
-# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Utility functions for test code that uses astroid ASTs as input."""
 import contextlib
@@ -48,9 +38,9 @@ def require_version(minver: str = "0.0.0", maxver: str = "4.0.0") -> Callable:
 
         @functools.wraps(f)
         def new_f(*args, **kwargs):
-            if minver != "0.0.0":
+            if current <= min_version:
                 pytest.skip(f"Needs Python > {minver}. Current version is {version}.")
-            elif maxver != "4.0.0":
+            elif current > max_version:
                 pytest.skip(f"Needs Python <= {maxver}. Current version is {version}.")
 
         return new_f
diff --git a/astroid/transforms.py b/astroid/transforms.py
index 42d0616..2fc8935 100644
--- a/astroid/transforms.py
+++ b/astroid/transforms.py
@@ -1,20 +1,15 @@
-# Copyright (c) 2015-2016, 2018 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import collections
-from functools import lru_cache
+from typing import TYPE_CHECKING
 
 from astroid.context import _invalidate_cache
 
+if TYPE_CHECKING:
+    from astroid import NodeNG
+
 
 class TransformVisitor:
     """A visitor for handling transforms.
@@ -25,13 +20,10 @@ class TransformVisitor:
     transforms for each encountered node.
     """
 
-    TRANSFORM_MAX_CACHE_SIZE = 10000
-
     def __init__(self):
         self.transforms = collections.defaultdict(list)
 
-    @lru_cache(maxsize=TRANSFORM_MAX_CACHE_SIZE)
-    def _transform(self, node):
+    def _transform(self, node: "NodeNG") -> "NodeNG":
         """Call matching transforms for the given node if any and return the
         transformed node.
         """
diff --git a/astroid/typing.py b/astroid/typing.py
new file mode 100644
index 0000000..32d01dd
--- /dev/null
+++ b/astroid/typing.py
@@ -0,0 +1,27 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
+import sys
+from typing import TYPE_CHECKING, Any, Callable
+
+if TYPE_CHECKING:
+    from astroid import nodes
+    from astroid.context import InferenceContext
+
+if sys.version_info >= (3, 8):
+    from typing import TypedDict
+else:
+    from typing_extensions import TypedDict
+
+
+class InferenceErrorInfo(TypedDict):
+    """Store additional Inference error information
+    raised with StopIteration exception.
+    """
+
+    node: "nodes.NodeNG"
+    context: "InferenceContext | None"
+
+
+InferFn = Callable[..., Any]
diff --git a/astroid/util.py b/astroid/util.py
index b54b2ec..b071285 100644
--- a/astroid/util.py
+++ b/astroid/util.py
@@ -1,14 +1,6 @@
-# Copyright (c) 2015-2018 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Bryce Guinta <bryce.guinta@protonmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import importlib
 import warnings
@@ -140,3 +132,16 @@ def proxy_alias(alias_name, node_type):
         },
     )
     return proxy(lambda: node_type)
+
+
+def check_warnings_filter() -> bool:
+    """Return True if any other than the default DeprecationWarning filter is enabled.
+
+    https://docs.python.org/3/library/warnings.html#default-warning-filter
+    """
+    return any(
+        issubclass(DeprecationWarning, filter[2])
+        and filter[0] != "ignore"
+        and filter[3] != "__main__"
+        for filter in warnings.filters
+    )
diff --git a/doc/conf.py b/doc/conf.py
index 4aba59d..8c5a03a 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 #
 # Astroid documentation build configuration file, created by
 # sphinx-quickstart on Wed Jun 26 15:00:40 2013.
diff --git a/doc/extending.rst b/doc/extending.rst
index 2580c10..7fedc16 100644
--- a/doc/extending.rst
+++ b/doc/extending.rst
@@ -95,7 +95,6 @@ Instantiating a new node might look as in::
 
     new_node = FunctionDef(
         name='my_new_function',
-        doc='the docstring of this function',
         lineno=3,
         col_offset=0,
         parent=the_parent_of_this_function,
@@ -104,6 +103,7 @@ Instantiating a new node might look as in::
         args=args,
         body=body,
         returns=returns,
+        doc_node=nodes.Const(value='the docstring of this function'),
     )
 
 
diff --git a/doc/inference.rst b/doc/inference.rst
index 008e65e..8d2c7e4 100644
--- a/doc/inference.rst
+++ b/doc/inference.rst
@@ -66,7 +66,7 @@ Most of the time you can access the same fields as those represented
 in the output of :meth:`repr_tree` so you can do ``tree.body[0].value.left``
 to get the left hand side operand of the addition operation.
 
-Another useful function that you can use is :func`astroid.extract_node`,
+Another useful function that you can use is :func:`astroid.extract_node`,
 which given a string, tries to extract one or more nodes from the given string::
 
    >>> node = astroid.extract_node('''
diff --git a/doc/release.md b/doc/release.md
index 96855ef..9052927 100644
--- a/doc/release.md
+++ b/doc/release.md
@@ -2,44 +2,79 @@
 
 So, you want to release the `X.Y.Z` version of astroid ?
 
-## Process
+## Releasing a major or minor version
 
-1. Check if the dependencies of the package are correct
-2. Check the result (Do `git diff vX.Y.Z-1 ChangeLog` in particular).
-3. Install the release dependencies `pip3 install pre-commit tbump`
-4. Bump the version and release by using `tbump X.Y.Z --no-push`.
-5. Push the tag.
-6. Release the version on GitHub with the same name as the tag and copy and paste the
-   appropriate changelog in the description. This trigger the pypi release.
+**Before releasing a major or minor version check if there are any unreleased commits on
+the maintenance branch. If so, release a last patch release first. See
+`Releasing a patch version`.**
 
-## Post release
+- Remove the empty changelog for the last unreleased patch version `X.Y-1.Z'`. (For
+  example: `v2.3.5`)
+- Check the result of `git diff vX.Y-1.Z' ChangeLog`. (For example:
+  `git diff v2.3.4 ChangeLog`)
+- Install the release dependencies: `pip3 install -r requirements_test.txt`
+- Bump the version and release by using `tbump X.Y.0 --no-push --no-tag`. (For example:
+  `tbump 2.4.0 --no-push --no-tag`)
+- Check the commit created with `git show` amend the commit if required.
+- Move the `main` branch up to a dev version with `tbump`:
 
-### Back to a dev version
+```bash
+tbump X.Y+1.0-dev0 --no-tag --no-push  # You can interrupt after the first step
+git commit -am "Upgrade the version to x.y+1.0-dev0 following x.y.0 release"
+```
 
-Move back to a dev version with `tbump`:
+For example:
 
 ```bash
-tbump X.Y.Z+1-dev0 --no-tag --no-push # You can interrupt during copyrite
-git commit -am "Upgrade the version to x.y.z+1-dev0 following x.y.z release"
+tbump 2.5.0-dev0 --no-tag --no-push
+git commit -am "Upgrade the version to 2.5.0-dev0 following 2.4.0 release"
 ```
 
-Check the result and then upgrade the main branch
+Check the commit and then push to a release branch
 
-### Milestone handling
+- 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.
+- Release the version on GitHub with the same name as the tag and copy and paste the
+  appropriate changelog in the description. This triggers the PyPI release.
+- Delete the `maintenance/X.Y-1.x` branch. (For example: `maintenance/2.3.x`)
+- Create a `maintenance/X.Y.x` (For example: `maintenance/2.4.x` from the `v2.4.0` tag.)
 
-We move issue that were not done in the next milestone and block release only if it's an
-issue labelled as blocker.
+## Backporting a fix from `main` to the maintenance branch
 
-## Post release
+Whenever a commit on `main` should be released in a patch release on the current
+maintenance branch we cherry-pick the commit from `main`.
 
-### Merge tags in main for pre-commit
+- During the merge request on `main`, make sure that the changelog is for the patch
+  version `X.Y-1.Z'`. (For example: `v2.3.5`)
+- After the PR is merged on `main` cherry-pick the commits on the `maintenance/X.Y.x`
+  branch (For example: from `maintenance/2.4.x` cherry-pick a commit from `main`)
+- Release a patch version
 
-If the tag you just made is not part of the main branch, merge the tag `vX.Y.Z` in the
-main branch by doing a history only merge. It's done in order to signal that this is an
-official release tag, and for `pre-commit autoupdate` to works.
+## Releasing a patch version
 
-```bash
-git checkout main
-git merge --no-edit --strategy=ours vX.Y.Z
-git push
-```
+We release patch versions when a crash or a bug is fixed on the main branch and has been
+cherry-picked on the maintenance branch.
+
+- Check the result of `git diff vX.Y-1.Z-1 ChangeLog`. (For example:
+  `git diff v2.3.4 ChangeLog`)
+- Install the release dependencies: `pip3 install -r requirements_test.txt`
+- Bump the version and release by using `tbump X.Y-1.Z --no-push`. (For example:
+  `tbump 2.3.5 --no-push`)
+- Check the result visually and then by triggering the "release tests" workflow in
+  GitHub Actions first.
+- Push the tag.
+- Release the version on GitHub with the same name as the tag and copy and paste the
+  appropriate changelog in the description. This triggers the PyPI release.
+- Merge the `maintenance/X.Y.x` branch on the main branch. The main branch should have
+  the changelog for `X.Y-1.Z+1` (For example `v2.3.6`). This merge is required so
+  `pre-commit autoupdate` works for pylint.
+- Fix version conflicts properly, or bump the version to `X.Y.0-devZ` (For example:
+  `2.4.0-dev6`) before pushing on the main branch
+
+## Milestone handling
+
+We move issues that were not done to the next milestone and block releases only if there
+are any open issues labelled as `blocker`.
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 3c1df6a..128a84f 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,2 +1,2 @@
 -e .
-sphinx~=4.0
+sphinx~=4.5
diff --git a/pylintrc b/pylintrc
index 2ca5a62..6f9dc1f 100644
--- a/pylintrc
+++ b/pylintrc
@@ -310,7 +310,7 @@ mixin-class-rgx=.*Mix[Ii]n
 # List of members which are set dynamically and missed by pylint inference
 # system, and so shouldn't trigger E0201 when accessed. Python regular
 # expressions are accepted.
-generated-members=REQUEST,acl_users,aq_parent
+generated-members=REQUEST,acl_users,aq_parent,argparse.Namespace
 
 
 [VARIABLES]
diff --git a/requirements_test.txt b/requirements_test.txt
index d1f4a1a..3b078c4 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -1,9 +1,10 @@
 -r requirements_test_min.txt
 -r requirements_test_pre_commit.txt
-coveralls~=3.0
+coveralls~=3.3
 coverage~=5.5
-pre-commit~=2.13
-pytest-cov~=2.11
+pre-commit~=2.17
+pytest-cov~=3.0
+contributors-txt>=0.7.3
 tbump~=6.3.2
 types-typed-ast; implementation_name=="cpython" and python_version<"3.8"
-types-pkg_resources==0.1.2
+types-pkg_resources==0.1.3
diff --git a/requirements_test_brain.txt b/requirements_test_brain.txt
index 1367d4e..9f7e134 100644
--- a/requirements_test_brain.txt
+++ b/requirements_test_brain.txt
@@ -1,8 +1,9 @@
 attrs
 types-attrs
 nose
-numpy
+numpy>=1.17.0
 python-dateutil
+PyQt6
 types-python-dateutil
 six
 types-six
diff --git a/requirements_test_pre_commit.txt b/requirements_test_pre_commit.txt
index f5677f0..8ed1a5f 100644
--- a/requirements_test_pre_commit.txt
+++ b/requirements_test_pre_commit.txt
@@ -1,6 +1,6 @@
-black==21.7b0
-pylint==2.12.2
-isort==5.9.2
+black==22.3.0
+pylint~=2.13.5
+isort==5.10.1
 flake8==4.0.1
-flake8-typing-imports==1.11.0
-mypy==0.930
+flake8-typing-imports==1.12.0
+mypy==0.940
diff --git a/script/.contributors_aliases.json b/script/.contributors_aliases.json
new file mode 100644
index 0000000..60fabfa
--- /dev/null
+++ b/script/.contributors_aliases.json
@@ -0,0 +1,173 @@
+{
+  "13665637+DanielNoord@users.noreply.github.com": {
+    "mails": ["13665637+DanielNoord@users.noreply.github.com"],
+    "name": "Daniël van Noord",
+    "team": "Maintainers"
+  },
+  "15907922+kasium@users.noreply.github.com": {
+    "mails": ["15907922+kasium@users.noreply.github.com"],
+    "name": "Kai Mueller"
+  },
+  "30130371+cdce8p@users.noreply.github.com": {
+    "mails": ["30130371+cdce8p@users.noreply.github.com"],
+    "name": "Marc Mueller",
+    "team": "Maintainers"
+  },
+  "androwiiid@gmail.com": {
+    "mails": ["androwiiid@gmail.com"],
+    "name": "Paligot Gérard"
+  },
+  "areveny@protonmail.com": {
+    "mails": ["areveny@protonmail.com", "self@areveny.com"],
+    "name": "Areveny",
+    "team": "Maintainers"
+  },
+  "ashley@awhetter.co.uk": {
+    "mails": [
+      "ashley@awhetter.co.uk",
+      "awhetter.2011@my.bristol.ac.uk",
+      "asw@dneg.com",
+      "AWhetter@users.noreply.github.com"
+    ],
+    "name": "Ashley Whetter",
+    "team": "Maintainers"
+  },
+  "bot@noreply.github.com": {
+    "mails": [
+      "66853113+pre-commit-ci[bot]@users.noreply.github.com",
+      "49699333+dependabot[bot]@users.noreply.github.com"
+    ],
+    "name": "bot"
+  },
+  "bryce.paul.guinta@gmail.com": {
+    "mails": ["bryce.paul.guinta@gmail.com", "bryce.guinta@protonmail.com"],
+    "name": "Bryce Guinta",
+    "team": "Maintainers"
+  },
+  "calen.pennington@gmail.com": {
+    "mails": ["cale@edx.org", "calen.pennington@gmail.com"],
+    "name": "Calen Pennington"
+  },
+  "ceridwenv@gmail.com": {
+    "mails": ["ceridwenv@gmail.com"],
+    "name": "Ceridwen",
+    "team": "Maintainers"
+  },
+  "contact@logilab.fr": {
+    "mails": [
+      "alexandre.fayolle@logilab.fr",
+      "emile.anclin@logilab.fr",
+      "david.douard@logilab.fr",
+      "laura.medioni@logilab.fr",
+      "anthony.truchet@logilab.fr",
+      "alain.leufroy@logilab.fr",
+      "julien.cristau@logilab.fr",
+      "Adrien.DiMascio@logilab.fr",
+      "emile@crater.logilab.fr",
+      "pierre-yves.david@logilab.fr",
+      "nicolas.chauvat@logilab.fr",
+      "afayolle.ml@free.fr",
+      "aurelien.campeas@logilab.fr",
+      "lmedioni@logilab.fr"
+    ],
+    "name": "LOGILAB S.A. (Paris, FRANCE)"
+  },
+  "dmand@yandex.ru": {
+    "mails": ["dmand@yandex.ru"],
+    "name": "Dimitri Prybysh",
+    "team": "Maintainers"
+  },
+  "github@euresti.com": {
+    "mails": ["david@dropbox.com", "github@euresti.com"],
+    "name": "David Euresti"
+  },
+  "guillaume.peillex@gmail.com": {
+    "mails": ["guillaume.peillex@gmail.com"],
+    "name": "Hippo91",
+    "team": "Maintainers"
+  },
+  "hugovk@users.noreply.github.com": {
+    "mails": ["hugovk@users.noreply.github.com"],
+    "name": "Hugo van Kemenade"
+  },
+  "jacob@bogdanov.dev": {
+    "mails": ["jacob@bogdanov.dev", "jbogdanov@128technology.com"],
+    "name": "Jacob Bogdanov"
+  },
+  "jacobtylerwalls@gmail.com": {
+    "mails": ["jacobtylerwalls@gmail.com"],
+    "name": "Jacob Walls",
+    "team": "Maintainers"
+  },
+  "joshdcannon@gmail.com": {
+    "mails": ["joshdcannon@gmail.com", "joshua.cannon@ni.com"],
+    "name": "Joshua Cannon"
+  },
+  "kavinsingh@hotmail.com": {
+    "mails": ["kavin.singh@mail.utoronto.ca", "kavinsingh@hotmail.com"],
+    "name": "Kavins Singh"
+  },
+  "keichi.t@me.com": {
+    "mails": ["hello@keichi.dev", "keichi.t@me.com"],
+    "name": "Keichi Takahashi"
+  },
+  "mariocj89@gmail.com": {
+    "mails": ["mcorcherojim@bloomberg.net", "mariocj89@gmail.com"],
+    "name": "Mario Corchero"
+  },
+  "me@the-compiler.org": {
+    "mails": ["me@the-compiler.org"],
+    "name": "Florian Bruhin",
+    "team": "Maintainers"
+  },
+  "moylop260@vauxoo.com": {
+    "mails": ["moylop260@vauxoo.com"],
+    "name": "Moises Lopez"
+  },
+  "no-reply@google.com": {
+    "mails": [
+      "nathaniel@google.com",
+      "mbp@google.com",
+      "balparda@google.com",
+      "dlindquist@google.com"
+    ],
+    "name": "Google, Inc."
+  },
+  "pcmanticore@gmail.com": {
+    "mails": ["cpopa@cloudbasesolutions.com", "pcmanticore@gmail.com"],
+    "name": "Claudiu Popa",
+    "team": "Ex-maintainers"
+  },
+  "pierre.sassoulas@gmail.com": {
+    "mails": ["pierre.sassoulas@gmail.com", "pierre.sassoulas@cea.fr"],
+    "name": "Pierre Sassoulas",
+    "team": "Maintainers"
+  },
+  "raphael@makeleaps.com": {
+    "mails": ["raphael@rtpg.co", "raphael@makeleaps.com"],
+    "name": "Raphael Gaschignard"
+  },
+  "rogalski.91@gmail.com": {
+    "mails": ["rogalski.91@gmail.com"],
+    "name": "Łukasz Rogalski",
+    "team": "Maintainers"
+  },
+  "shlomme@gmail.com": {
+    "mails": ["shlomme@gmail.com", "tmarek@google.com"],
+    "name": "Torsten Marek",
+    "team": "Ex-maintainers"
+  },
+  "stefan@sofa-rockers.org": {
+    "mails": ["stefan.scherfke@energymeteo.de", "stefan@sofa-rockers.org"],
+    "name": "Stefan Scherfke"
+  },
+  "thenault@gmail.com": {
+    "mails": ["thenault@gmail.com", "sylvain.thenault@logilab.fr"],
+    "name": "Sylvain Thénault",
+    "team": "Ex-maintainers"
+  },
+  "ville.skytta@iki.fi": {
+    "mails": ["ville.skytta@iki.fi", "ville.skytta@upcloud.com"],
+    "name": "Ville Skyttä"
+  }
+}
diff --git a/script/bump_changelog.py b/script/bump_changelog.py
index 5b66735..78e3bef 100644
--- a/script/bump_changelog.py
+++ b/script/bump_changelog.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """
 This script permits to upgrade the changelog in astroid or pylint when releasing a version.
 """
diff --git a/script/copyright.txt b/script/copyright.txt
new file mode 100644
index 0000000..25341a5
--- /dev/null
+++ b/script/copyright.txt
@@ -0,0 +1,3 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
diff --git a/script/create_contributor_list.py b/script/create_contributor_list.py
new file mode 100644
index 0000000..6e95948
--- /dev/null
+++ b/script/create_contributor_list.py
@@ -0,0 +1,21 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
+from pathlib import Path
+
+from contributors_txt import create_contributors_txt
+
+ASTROID_BASE_DIRECTORY = Path(__file__).parent.parent
+ALIASES_FILE = ASTROID_BASE_DIRECTORY / "script/.contributors_aliases.json"
+DEFAULT_CONTRIBUTOR_PATH = ASTROID_BASE_DIRECTORY / "CONTRIBUTORS.txt"
+
+
+def main():
+    create_contributors_txt(
+        aliases_file=ALIASES_FILE, output=DEFAULT_CONTRIBUTOR_PATH, verbose=True
+    )
+
+
+if __name__ == "__main__":
+    main()
diff --git a/script/test_bump_changelog.py b/script/test_bump_changelog.py
index 2a60e35..8ed3640 100644
--- a/script/test_bump_changelog.py
+++ b/script/test_bump_changelog.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import logging
 
 import pytest
diff --git a/setup.cfg b/setup.cfg
index 2724b33..e30d844 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -8,7 +8,9 @@ url = https://github.com/PyCQA/astroid
 author = Python Code Quality Authority
 author_email = code-quality@python.org
 license = LGPL-2.1-or-later
-license_files = LICENSE
+license_files =
+    LICENSE
+    CONTRIBUTORS.txt
 classifiers =
     Development Status :: 6 - Mature
     Environment :: Console
@@ -37,7 +39,7 @@ project_urls =
 packages = find:
 install_requires =
     lazy_object_proxy>=1.4.0
-    wrapt>=1.11,<1.14
+    wrapt>=1.11,<2
     setuptools>=20.0
     typed-ast>=1.4.0,<2.0;implementation_name=="cpython" and python_version<"3.8"
     typing-extensions>=3.10;python_version<"3.10"
@@ -68,6 +70,7 @@ scripts_are_modules = True
 no_implicit_optional = True
 warn_redundant_casts = True
 show_error_codes = True
+enable_error_code = ignore-without-code
 
 [mypy-setuptools]
 ignore_missing_imports = True
diff --git a/tbump.toml b/tbump.toml
index 60d7ccb..71b61aa 100644
--- a/tbump.toml
+++ b/tbump.toml
@@ -1,7 +1,7 @@
 github_url = "https://github.com/PyCQA/astroid"
 
 [version]
-current = "2.9.3"
+current = "2.11.3"
 regex = '''
 ^(?P<major>0|[1-9]\d*)
 \.
@@ -29,8 +29,12 @@ name = "Upgrade changelog changelog"
 cmd = "python3 script/bump_changelog.py {new_version}"
 
 [[before_commit]]
-name = "Upgrade copyrights"
-cmd = "pip3 install copyrite;copyrite --contribution-threshold 1 --change-threshold 3 --backend-type git --aliases=.copyrite_aliases . --jobs=8"
+name = "Normalize the contributors-txt configuration"
+cmd = "contributors-txt-normalize-configuration -a script/.contributors_aliases.json -o script/.contributors_aliases.json"
+
+[[before_commit]]
+name = "Upgrade the contributors list"
+cmd = "python3 script/create_contributor_list.py"
 
 [[before_commit]]
 name = "Apply pre-commit"
diff --git a/tests/resources.py b/tests/resources.py
index ebb6f7a..23f5a7a 100644
--- a/tests/resources.py
+++ b/tests/resources.py
@@ -1,26 +1,18 @@
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 David Cain <davidjosephcain@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import os
 import sys
+from pathlib import Path
 from typing import Optional
 
 from astroid import builder
 from astroid.manager import AstroidManager
 from astroid.nodes.scoped_nodes import Module
 
-DATA_DIR = os.path.join("testdata", "python3")
-RESOURCE_PATH = os.path.join(os.path.dirname(__file__), DATA_DIR, "data")
+DATA_DIR = Path("testdata") / "python3"
+RESOURCE_PATH = Path(__file__).parent / DATA_DIR / "data"
 
 
 def find(name: str) -> str:
diff --git a/tests/testdata/python3/data/max_inferable_limit_for_classes/main.py b/tests/testdata/python3/data/max_inferable_limit_for_classes/main.py
new file mode 100644
index 0000000..2588d91
--- /dev/null
+++ b/tests/testdata/python3/data/max_inferable_limit_for_classes/main.py
@@ -0,0 +1,38 @@
+"""This example is based on sqlalchemy.
+
+See https://github.com/PyCQA/pylint/issues/5679
+"""
+from other_funcs import FromClause
+
+from .nodes import roles
+
+
+class HasMemoized(object):
+    ...
+
+
+class Generative(HasMemoized):
+    ...
+
+
+class ColumnElement(
+    roles.ColumnArgumentOrKeyRole,
+    roles.BinaryElementRole,
+    roles.OrderByRole,
+    roles.ColumnsClauseRole,
+    roles.LimitOffsetRole,
+    roles.DMLColumnRole,
+    roles.DDLConstraintColumnRole,
+    roles.StatementRole,
+    Generative,
+):
+    ...
+
+
+class FunctionElement(ColumnElement, FromClause):
+    ...
+
+
+class months_between(FunctionElement):
+    def __init__(self):
+        super().__init__()
diff --git a/tests/testdata/python3/data/max_inferable_limit_for_classes/nodes/roles.py b/tests/testdata/python3/data/max_inferable_limit_for_classes/nodes/roles.py
new file mode 100644
index 0000000..2f58f1b
--- /dev/null
+++ b/tests/testdata/python3/data/max_inferable_limit_for_classes/nodes/roles.py
@@ -0,0 +1,82 @@
+class SQLRole(object):
+    ...
+
+
+class UsesInspection(object):
+    ...
+
+
+class AllowsLambdaRole(object):
+    ...
+
+
+class ColumnArgumentRole(SQLRole):
+    ...
+
+
+class ColumnArgumentOrKeyRole(ColumnArgumentRole):
+    ...
+
+
+class ColumnListRole(SQLRole):
+    ...
+
+
+class ColumnsClauseRole(AllowsLambdaRole, UsesInspection, ColumnListRole):
+    ...
+
+
+class LimitOffsetRole(SQLRole):
+    ...
+
+
+class ByOfRole(ColumnListRole):
+    ...
+
+
+class OrderByRole(AllowsLambdaRole, ByOfRole):
+    ...
+
+
+class StructuralRole(SQLRole):
+    ...
+
+
+class ExpressionElementRole(SQLRole):
+    ...
+
+
+class BinaryElementRole(ExpressionElementRole):
+    ...
+
+
+class JoinTargetRole(AllowsLambdaRole, UsesInspection, StructuralRole):
+    ...
+
+
+class FromClauseRole(ColumnsClauseRole, JoinTargetRole):
+    ...
+
+
+class StrictFromClauseRole(FromClauseRole):
+    ...
+
+
+class AnonymizedFromClauseRole(StrictFromClauseRole):
+    ...
+
+
+class ReturnsRowsRole(SQLRole):
+    ...
+
+
+class StatementRole(SQLRole):
+    ...
+
+
+class DMLColumnRole(SQLRole):
+    ...
+
+
+class DDLConstraintColumnRole(SQLRole):
+    ...
diff --git a/tests/testdata/python3/data/max_inferable_limit_for_classes/other_funcs.py b/tests/testdata/python3/data/max_inferable_limit_for_classes/other_funcs.py
new file mode 100644
index 0000000..f737fbf
--- /dev/null
+++ b/tests/testdata/python3/data/max_inferable_limit_for_classes/other_funcs.py
@@ -0,0 +1,31 @@
+from operator import attrgetter
+
+from .nodes import roles
+
+
+class HasCacheKey(object):
+    ...
+
+
+class HasMemoized(object):
+    ...
+
+
+class MemoizedHasCacheKey(HasCacheKey, HasMemoized):
+    ...
+
+
+class ClauseElement(MemoizedHasCacheKey):
+    ...
+
+
+class ReturnsRows(roles.ReturnsRowsRole, ClauseElement):
+    ...
+
+
+class Selectable(ReturnsRows):
+    ...
+
+
+class FromClause(roles.AnonymizedFromClauseRole, Selectable):
+    c = property(attrgetter("columns"))
diff --git a/tests/testdata/python3/data/module_dict_items_call/models.py b/tests/testdata/python3/data/module_dict_items_call/models.py
new file mode 100644
index 0000000..212bc01
--- /dev/null
+++ b/tests/testdata/python3/data/module_dict_items_call/models.py
@@ -0,0 +1,5 @@
+import re
+
+
+class MyModel:
+    class_attribute = 1
diff --git a/tests/testdata/python3/data/module_dict_items_call/test.py b/tests/testdata/python3/data/module_dict_items_call/test.py
new file mode 100644
index 0000000..4a52b18
--- /dev/null
+++ b/tests/testdata/python3/data/module_dict_items_call/test.py
@@ -0,0 +1,7 @@
+import models
+
+
+def func():
+    for _, value in models.__dict__.items():
+        if isinstance(value, type):
+            value.class_attribute += 1
diff --git a/tests/testdata/python3/data/recursion.py b/tests/testdata/python3/data/recursion.py
index a34dad3..85f6513 100644
--- a/tests/testdata/python3/data/recursion.py
+++ b/tests/testdata/python3/data/recursion.py
@@ -1,3 +1,3 @@
-""" For issue #25 """
-class Base(object):
+""" For issue #25 """
+class Base(object):
     pass
\ No newline at end of file
diff --git a/tests/unittest_brain.py b/tests/unittest_brain.py
index 71cee38..989cebd 100644
--- a/tests/unittest_brain.py
+++ b/tests/unittest_brain.py
@@ -1,47 +1,6 @@
-# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2015 raylu <lurayl@gmail.com>
-# Copyright (c) 2015 Philip Lorenz <philip@bithub.de>
-# Copyright (c) 2016 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2017-2018, 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 David Euresti <github@euresti.com>
-# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2018, 2021 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018 Tomas Gavenciak <gavento@ucw.cz>
-# Copyright (c) 2018 David Poirier <david-poirier-csn@users.noreply.github.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2018 Ioana Tagirta <ioana.tagirta@gmail.com>
-# Copyright (c) 2018 Ahmed Azzaoui <ahmed.azzaoui@engie.com>
-# Copyright (c) 2019-2020 Bryce Guinta <bryce.guinta@protonmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2019 Tomas Novak <ext.Tomas.Novak@skoda-auto.cz>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2019 Grygorii Iermolenko <gyermolenko@gmail.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2020 Peter Kolbus <peter.kolbus@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Kian Meng, Ang <kianmeng.ang@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Joshua Cannon <joshua.cannon@ni.com>
-# Copyright (c) 2021 Craig Franklin <craigjfranklin@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Jonathan Striebel <jstriebel@users.noreply.github.com>
-# Copyright (c) 2021 Dimitri Prybysh <dmand@yandex.ru>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
-# Copyright (c) 2021 Alphadelta14 <alpha@alphaservcomputing.solutions>
-# Copyright (c) 2021 Tim Martin <tim@asymptotic.co.uk>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-# Copyright (c) 2021 Artsiom Kaval <lezeroq@gmail.com>
-# Copyright (c) 2021 Damien Baty <damien@damienbaty.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Tests for basic functionality in astroid.brain."""
 import io
@@ -57,7 +16,11 @@ import astroid
 from astroid import MANAGER, bases, builder, nodes, objects, test_utils, util
 from astroid.bases import Instance
 from astroid.const import PY37_PLUS
-from astroid.exceptions import AttributeInferenceError, InferenceError
+from astroid.exceptions import (
+    AttributeInferenceError,
+    InferenceError,
+    UseInferenceDefault,
+)
 from astroid.nodes.node_classes import Const
 from astroid.nodes.scoped_nodes import ClassDef
 
@@ -196,6 +159,8 @@ class NamedTupleTest(unittest.TestCase):
         self.assertEqual(
             [anc.name for anc in klass.ancestors()], ["X", "tuple", "object"]
         )
+        # See: https://github.com/PyCQA/pylint/issues/5982
+        self.assertNotIn("X", klass.locals)
         for anc in klass.ancestors():
             self.assertFalse(anc.parent is None)
 
@@ -481,7 +446,7 @@ class ModuleExtenderTest(unittest.TestCase):
     def test_extension_modules(self) -> None:
         transformer = MANAGER._transform
         for extender, _ in transformer.transforms[nodes.Module]:
-            n = nodes.Module("__main__", None)
+            n = nodes.Module("__main__")
             extender(n)
 
 
@@ -1660,6 +1625,19 @@ class TypingBrain(unittest.TestCase):
             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/PyCQA/pylint/issues/5770"""
+        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(
             """
@@ -2211,6 +2189,73 @@ class AttrsTest(unittest.TestCase):
             should_be_unknown = next(module.getattr(name)[0].infer()).getattr("d")[0]
             self.assertIsInstance(should_be_unknown, astroid.Unknown)
 
+    def test_attrs_transform(self) -> None:
+        """Test brain for decorators of the 'attrs' package.
+
+        Package added support for 'attrs' a long side 'attr' in v21.3.0.
+        See: https://github.com/python-attrs/attrs/releases/tag/21.3.0
+        """
+        module = astroid.parse(
+            """
+        import attrs
+        from attrs import field, mutable, frozen
+
+        @attrs.define
+        class Foo:
+
+            d = attrs.field(attrs.Factory(dict))
+
+        f = Foo()
+        f.d['answer'] = 42
+
+        @attrs.define(slots=True)
+        class Bar:
+            d = field(attrs.Factory(dict))
+
+        g = Bar()
+        g.d['answer'] = 42
+
+        @attrs.mutable
+        class Bah:
+            d = field(attrs.Factory(dict))
+
+        h = Bah()
+        h.d['answer'] = 42
+
+        @attrs.frozen
+        class Bai:
+            d = attrs.field(attrs.Factory(dict))
+
+        i = Bai()
+        i.d['answer'] = 42
+
+        @attrs.define
+        class Spam:
+            d = field(default=attrs.Factory(dict))
+
+        j = Spam(d=1)
+        j.d['answer'] = 42
+
+        @attrs.mutable
+        class Eggs:
+            d = attrs.field(default=attrs.Factory(dict))
+
+        k = Eggs(d=1)
+        k.d['answer'] = 42
+
+        @attrs.frozen
+        class Eggs:
+            d = attrs.field(default=attrs.Factory(dict))
+
+        l = Eggs(d=1)
+        l.d['answer'] = 42
+        """
+        )
+
+        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)
+
     def test_special_attributes(self) -> None:
         """Make sure special attrs attributes exist"""
 
@@ -2907,6 +2952,30 @@ def test_infer_dict_from_keys() -> None:
 
 
 class TestFunctoolsPartial:
+    @staticmethod
+    def test_infer_partial() -> None:
+        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
+        partial = inferred[0]
+        assert isinstance(partial, objects.PartialFunction)
+        assert isinstance(partial.doc_node, nodes.Const)
+        assert partial.doc_node.value == "Docstring"
+        with pytest.warns(DeprecationWarning) as records:
+            assert partial.doc == "Docstring"
+            assert len(records) == 1
+        assert partial.lineno == 3
+        assert partial.col_offset == 0
+
     def test_invalid_functools_partial_calls(self) -> None:
         ast_nodes = astroid.extract_node(
             """
diff --git a/tests/unittest_brain_builtin.py b/tests/unittest_brain_builtin.py
new file mode 100644
index 0000000..0d74930
--- /dev/null
+++ b/tests/unittest_brain_builtin.py
@@ -0,0 +1,24 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
+"""Unit Tests for the builtins brain module."""
+
+import unittest
+
+from astroid import extract_node, objects
+
+
+class BuiltinsTest(unittest.TestCase):
+    def test_infer_property(self):
+        class_with_property = extract_node(
+            """
+        class Something:
+            def getter():
+                return 5
+            asd = property(getter) #@
+        """
+        )
+        inferred_property = list(class_with_property.value.infer())[0]
+        self.assertTrue(isinstance(inferred_property, objects.Property))
+        self.assertTrue(hasattr(inferred_property, "args"))
diff --git a/tests/unittest_brain_ctypes.py b/tests/unittest_brain_ctypes.py
index ee0213d..cae9540 100644
--- a/tests/unittest_brain_ctypes.py
+++ b/tests/unittest_brain_ctypes.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import sys
 
 import pytest
diff --git a/tests/unittest_brain_dataclasses.py b/tests/unittest_brain_dataclasses.py
index 2054aeb..fa0a204 100644
--- a/tests/unittest_brain_dataclasses.py
+++ b/tests/unittest_brain_dataclasses.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import pytest
 
 import astroid
@@ -184,6 +188,7 @@ def test_inference_no_annotation(module: str):
     inferred = next(class_def.infer())
     assert isinstance(inferred, nodes.ClassDef)
     assert inferred.instance_attrs == {}
+    assert inferred.is_dataclass
 
     # Both the class and instance can still access the attribute
     for node in (klass, instance):
@@ -216,6 +221,7 @@ def test_inference_class_var(module: str):
     inferred = next(class_def.infer())
     assert isinstance(inferred, nodes.ClassDef)
     assert inferred.instance_attrs == {}
+    assert inferred.is_dataclass
 
     # Both the class and instance can still access the attribute
     for node in (klass, instance):
@@ -248,6 +254,7 @@ def test_inference_init_var(module: str):
     inferred = next(class_def.infer())
     assert isinstance(inferred, nodes.ClassDef)
     assert inferred.instance_attrs == {}
+    assert inferred.is_dataclass
 
     # Both the class and instance can still access the attribute
     for node in (klass, instance):
@@ -666,6 +673,7 @@ def test_annotated_enclosed_field_call(module: str):
     inferred = node.inferred()
     assert len(inferred) == 1 and isinstance(inferred[0], nodes.ClassDef)
     assert "attribute" in inferred[0].instance_attrs
+    assert inferred[0].is_dataclass
 
 
 @parametrize_module
@@ -683,3 +691,30 @@ def test_invalid_field_call(module: str) -> None:
     inferred = code.inferred()
     assert len(inferred) == 1
     assert isinstance(inferred[0], nodes.ClassDef)
+    assert inferred[0].is_dataclass
+
+
+def test_non_dataclass_is_not_dataclass() -> None:
+    """Test that something that isn't a dataclass has the correct attribute."""
+    module = astroid.parse(
+        """
+    class A:
+        val: field()
+
+    def dataclass():
+        return
+
+    @dataclass
+    class B:
+        val: field()
+    """
+    )
+    class_a = module.body[0].inferred()
+    assert len(class_a) == 1
+    assert isinstance(class_a[0], nodes.ClassDef)
+    assert not class_a[0].is_dataclass
+
+    class_b = module.body[2].inferred()
+    assert len(class_b) == 1
+    assert isinstance(class_b[0], nodes.ClassDef)
+    assert not class_b[0].is_dataclass
diff --git a/tests/unittest_brain_numpy_core_fromnumeric.py b/tests/unittest_brain_numpy_core_fromnumeric.py
index 417fc80..bbb6ba9 100644
--- a/tests/unittest_brain_numpy_core_fromnumeric.py
+++ b/tests/unittest_brain_numpy_core_fromnumeric.py
@@ -1,12 +1,7 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import unittest
 
 try:
diff --git a/tests/unittest_brain_numpy_core_function_base.py b/tests/unittest_brain_numpy_core_function_base.py
index 2918220..f0d561d 100644
--- a/tests/unittest_brain_numpy_core_function_base.py
+++ b/tests/unittest_brain_numpy_core_function_base.py
@@ -1,12 +1,7 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import unittest
 
 try:
diff --git a/tests/unittest_brain_numpy_core_multiarray.py b/tests/unittest_brain_numpy_core_multiarray.py
index ef96aa2..bbf2497 100644
--- a/tests/unittest_brain_numpy_core_multiarray.py
+++ b/tests/unittest_brain_numpy_core_multiarray.py
@@ -1,12 +1,7 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import unittest
 
 try:
diff --git a/tests/unittest_brain_numpy_core_numeric.py b/tests/unittest_brain_numpy_core_numeric.py
index 343de67..197c3b6 100644
--- a/tests/unittest_brain_numpy_core_numeric.py
+++ b/tests/unittest_brain_numpy_core_numeric.py
@@ -1,13 +1,11 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import unittest
+from typing import List
+
+import pytest
 
 try:
     import numpy  # pylint: disable=unused-import
@@ -62,5 +60,26 @@ class BrainNumpyCoreNumericTest(unittest.TestCase):
                 )
 
 
+@pytest.mark.skipif(not HAS_NUMPY, reason="This test requires the numpy library.")
+@pytest.mark.parametrize(
+    "method, expected_args",
+    [
+        ("zeros_like", ["a", "dtype", "order", "subok", "shape"]),
+        ("full_like", ["a", "fill_value", "dtype", "order", "subok", "shape"]),
+        ("ones_like", ["a", "dtype", "order", "subok", "shape"]),
+        ("ones", ["shape", "dtype", "order"]),
+    ],
+)
+def test_function_parameters(method: str, expected_args: List[str]) -> None:
+    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
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/unittest_brain_numpy_core_numerictypes.py b/tests/unittest_brain_numpy_core_numerictypes.py
index ebfe8a2..2ed91c1 100644
--- a/tests/unittest_brain_numpy_core_numerictypes.py
+++ b/tests/unittest_brain_numpy_core_numerictypes.py
@@ -1,12 +1,7 @@
-# Copyright (c) 2017-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2017-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import unittest
 
 try:
diff --git a/tests/unittest_brain_numpy_core_umath.py b/tests/unittest_brain_numpy_core_umath.py
index c80c391..27d79a9 100644
--- a/tests/unittest_brain_numpy_core_umath.py
+++ b/tests/unittest_brain_numpy_core_umath.py
@@ -1,13 +1,7 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import unittest
 
 try:
diff --git a/tests/unittest_brain_numpy_ma.py b/tests/unittest_brain_numpy_ma.py
index 96dddd2..830e729 100644
--- a/tests/unittest_brain_numpy_ma.py
+++ b/tests/unittest_brain_numpy_ma.py
@@ -1,7 +1,7 @@
-# Copyright (c) 2021 hippo91 <guillaume.peillex@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import pytest
 
 try:
diff --git a/tests/unittest_brain_numpy_ndarray.py b/tests/unittest_brain_numpy_ndarray.py
index 1a417b8..fb216de 100644
--- a/tests/unittest_brain_numpy_ndarray.py
+++ b/tests/unittest_brain_numpy_ndarray.py
@@ -1,13 +1,7 @@
-# Copyright (c) 2017-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2017-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import unittest
 
 try:
diff --git a/tests/unittest_brain_numpy_random_mtrand.py b/tests/unittest_brain_numpy_random_mtrand.py
index de374b9..beb8fb6 100644
--- a/tests/unittest_brain_numpy_random_mtrand.py
+++ b/tests/unittest_brain_numpy_random_mtrand.py
@@ -1,11 +1,7 @@
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import unittest
 
 try:
diff --git a/tests/unittest_brain_qt.py b/tests/unittest_brain_qt.py
new file mode 100644
index 0000000..18e0d6d
--- /dev/null
+++ b/tests/unittest_brain_qt.py
@@ -0,0 +1,38 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
+from importlib.util import find_spec
+
+import pytest
+
+from astroid import Uninferable, extract_node
+from astroid.bases import UnboundMethod
+from astroid.manager import AstroidManager
+from astroid.nodes import FunctionDef
+
+HAS_PYQT6 = find_spec("PyQt6")
+
+
+@pytest.mark.skipif(HAS_PYQT6 is None, reason="This test requires the PyQt6 library.")
+class TestBrainQt:
+    @staticmethod
+    def test_value_of_lambda_instance_attrs_is_list():
+        """Regression test for https://github.com/PyCQA/pylint/issues/6221
+
+        A crash occurred in pylint when a nodes.FunctionDef was iterated directly,
+        giving items like "self" instead of iterating a one-element list containing
+        the wanted nodes.FunctionDef.
+        """
+        src = """
+        from PyQt6 import QtPrintSupport as printsupport
+        printsupport.QPrintPreviewDialog.paintRequested  #@
+        """
+        AstroidManager.brain["extension_package_whitelist"] = {"PyQt6.QtPrintSupport"}
+        node = extract_node(src)
+        attribute_node = node.inferred()[0]
+        if attribute_node is Uninferable:
+            pytest.skip("PyQt6 C bindings may not be installed?")
+        assert isinstance(attribute_node, UnboundMethod)
+        # scoped_nodes.Lambda.instance_attrs is typed as Dict[str, List[NodeNG]]
+        assert isinstance(attribute_node.instance_attrs["connect"][0], FunctionDef)
diff --git a/tests/unittest_brain_signal.py b/tests/unittest_brain_signal.py
index 5422ecf..fdd4f42 100644
--- a/tests/unittest_brain_signal.py
+++ b/tests/unittest_brain_signal.py
@@ -1,5 +1,7 @@
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """Unit Tests for the signal brain module."""
 
 
diff --git a/tests/unittest_brain_unittest.py b/tests/unittest_brain_unittest.py
index 644614d..d8d638a 100644
--- a/tests/unittest_brain_unittest.py
+++ b/tests/unittest_brain_unittest.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import unittest
 
 from astroid import builder
diff --git a/tests/unittest_builder.py b/tests/unittest_builder.py
index eb7fbdc..58ff711 100644
--- a/tests/unittest_builder.py
+++ b/tests/unittest_builder.py
@@ -1,40 +1,25 @@
-# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014-2015 Google, Inc.
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2017 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 Kian Meng, Ang <kianmeng.ang@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-# Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """tests for the astroid builder and rebuilder module"""
 
 import collections
+import importlib
 import os
+import pathlib
+import py_compile
 import socket
 import sys
+import tempfile
+import textwrap
 import unittest
+import unittest.mock
 
 import pytest
 
 from astroid import Instance, builder, nodes, test_utils, util
-from astroid.const import PY38_PLUS
+from astroid.const import IS_PYPY, PY38, PY38_PLUS, PY39_PLUS
 from astroid.exceptions import (
     AstroidBuildingError,
     AstroidSyntaxError,
@@ -70,12 +55,18 @@ class FromToLineNoTest(unittest.TestCase):
         self.assertEqual(name.tolineno, 4)
         strarg = callfunc.args[0]
         self.assertIsInstance(strarg, nodes.Const)
-        if hasattr(sys, "pypy_version_info"):
-            lineno = 4
+        if IS_PYPY:
+            self.assertEqual(strarg.fromlineno, 4)
+            if not PY39_PLUS:
+                self.assertEqual(strarg.tolineno, 4)
+            else:
+                self.assertEqual(strarg.tolineno, 5)
         else:
-            lineno = 5 if not PY38_PLUS else 4
-        self.assertEqual(strarg.fromlineno, lineno)
-        self.assertEqual(strarg.tolineno, lineno)
+            if not PY38_PLUS:
+                self.assertEqual(strarg.fromlineno, 5)
+            else:
+                self.assertEqual(strarg.fromlineno, 4)
+            self.assertEqual(strarg.tolineno, 5)
         namearg = callfunc.args[1]
         self.assertIsInstance(namearg, nodes.Name)
         self.assertEqual(namearg.fromlineno, 5)
@@ -132,12 +123,162 @@ class FromToLineNoTest(unittest.TestCase):
             __name__,
         )
         function = astroid["function"]
-        # XXX discussable, but that's what is expected by pylint right now
+        # XXX discussable, but that's what is expected by pylint right now, similar to ClassDef
         self.assertEqual(function.fromlineno, 3)
         self.assertEqual(function.tolineno, 5)
         self.assertEqual(function.decorators.fromlineno, 2)
         self.assertEqual(function.decorators.tolineno, 2)
 
+    @staticmethod
+    def test_decorated_class_lineno() -> None:
+        code = textwrap.dedent(
+            """
+        class A:
+            ...
+
+        @decorator
+        class B:
+            ...
+
+        @deco1
+        @deco2(
+            var=42
+        )
+        class C:
+            ...
+        """
+        )
+
+        ast_module: nodes.Module = builder.parse(code)  # type: ignore[assignment]
+
+        a = ast_module.body[0]
+        assert isinstance(a, nodes.ClassDef)
+        assert a.fromlineno == 2
+        assert a.tolineno == 3
+
+        b = ast_module.body[1]
+        assert isinstance(b, nodes.ClassDef)
+        assert b.fromlineno == 6
+        assert b.tolineno == 7
+
+        c = ast_module.body[2]
+        assert isinstance(c, nodes.ClassDef)
+        if not PY38_PLUS or PY38 and IS_PYPY:
+            # Not perfect, but best we can do for Python 3.7 and PyPy 3.8
+            # Can't detect closing bracket on new line.
+            assert c.fromlineno == 12
+        else:
+            assert c.fromlineno == 13
+        assert c.tolineno == 14
+
+    @staticmethod
+    def test_class_with_docstring() -> None:
+        """Test class nodes which only have docstrings."""
+        code = textwrap.dedent(
+            '''\
+        class A:
+            """My docstring"""
+            var = 1
+
+        class B:
+            """My docstring"""
+
+        class C:
+            """My docstring
+            is long."""
+
+        class D:
+            """My docstring
+            is long.
+            """
+
+        class E:
+            ...
+        '''
+        )
+
+        ast_module = builder.parse(code)
+
+        a = ast_module.body[0]
+        assert isinstance(a, nodes.ClassDef)
+        assert a.fromlineno == 1
+        assert a.tolineno == 3
+
+        b = ast_module.body[1]
+        assert isinstance(b, nodes.ClassDef)
+        assert b.fromlineno == 5
+        assert b.tolineno == 6
+
+        c = ast_module.body[2]
+        assert isinstance(c, nodes.ClassDef)
+        assert c.fromlineno == 8
+        assert c.tolineno == 10
+
+        d = ast_module.body[3]
+        assert isinstance(d, nodes.ClassDef)
+        assert d.fromlineno == 12
+        assert d.tolineno == 15
+
+        e = ast_module.body[4]
+        assert isinstance(d, nodes.ClassDef)
+        assert e.fromlineno == 17
+        assert e.tolineno == 18
+
+    @staticmethod
+    def test_function_with_docstring() -> None:
+        """Test function defintions with only docstrings."""
+        code = textwrap.dedent(
+            '''\
+        def a():
+            """My docstring"""
+            var = 1
+
+        def b():
+            """My docstring"""
+
+        def c():
+            """My docstring
+            is long."""
+
+        def d():
+            """My docstring
+            is long.
+            """
+
+        def e(a, b):
+            """My docstring
+            is long.
+            """
+        '''
+        )
+
+        ast_module = builder.parse(code)
+
+        a = ast_module.body[0]
+        assert isinstance(a, nodes.FunctionDef)
+        assert a.fromlineno == 1
+        assert a.tolineno == 3
+
+        b = ast_module.body[1]
+        assert isinstance(b, nodes.FunctionDef)
+        assert b.fromlineno == 5
+        assert b.tolineno == 6
+
+        c = ast_module.body[2]
+        assert isinstance(c, nodes.FunctionDef)
+        assert c.fromlineno == 8
+        assert c.tolineno == 10
+
+        d = ast_module.body[3]
+        assert isinstance(d, nodes.FunctionDef)
+        assert d.fromlineno == 12
+        assert d.tolineno == 15
+
+        e = ast_module.body[4]
+        assert isinstance(e, nodes.FunctionDef)
+        assert e.fromlineno == 17
+        assert e.tolineno == 20
+
     def test_class_lineno(self) -> None:
         stmts = self.astroid.body
         # on line 20:
@@ -554,6 +695,7 @@ class BuilderTest(unittest.TestCase):
                 a.custom_attr = 0
             """
         builder.parse(code)
+        # pylint: disable=no-member
         nonetype = nodes.const_factory(None)
         self.assertNotIn("custom_attr", nonetype.locals)
         self.assertNotIn("custom_attr", nonetype.instance_attrs)
@@ -607,7 +749,11 @@ class FileBuildTest(unittest.TestCase):
         """test base properties and method of an astroid module"""
         module = self.module
         self.assertEqual(module.name, "data.module")
-        self.assertEqual(module.doc, "test module for astroid\n")
+        with pytest.warns(DeprecationWarning) as records:
+            self.assertEqual(module.doc, "test module for astroid\n")
+            assert len(records) == 1
+        assert isinstance(module.doc_node, nodes.Const)
+        self.assertEqual(module.doc_node.value, "test module for astroid\n")
         self.assertEqual(module.fromlineno, 0)
         self.assertIsNone(module.parent)
         self.assertEqual(module.frame(), module)
@@ -649,7 +795,11 @@ class FileBuildTest(unittest.TestCase):
         module = self.module
         function = module["global_access"]
         self.assertEqual(function.name, "global_access")
-        self.assertEqual(function.doc, "function test")
+        with pytest.warns(DeprecationWarning) as records:
+            self.assertEqual(function.doc, "function test")
+            assert len(records)
+        assert isinstance(function.doc_node, nodes.Const)
+        self.assertEqual(function.doc_node.value, "function test")
         self.assertEqual(function.fromlineno, 11)
         self.assertTrue(function.parent)
         self.assertEqual(function.frame(), function)
@@ -672,7 +822,11 @@ class FileBuildTest(unittest.TestCase):
         module = self.module
         klass = module["YO"]
         self.assertEqual(klass.name, "YO")
-        self.assertEqual(klass.doc, "hehe\n    haha")
+        with pytest.warns(DeprecationWarning) as records:
+            self.assertEqual(klass.doc, "hehe\n    haha")
+            assert len(records) == 1
+        assert isinstance(klass.doc_node, nodes.Const)
+        self.assertEqual(klass.doc_node.value, "hehe\n    haha")
         self.assertEqual(klass.fromlineno, 25)
         self.assertTrue(klass.parent)
         self.assertEqual(klass.frame(), klass)
@@ -726,7 +880,11 @@ class FileBuildTest(unittest.TestCase):
         method = klass2["method"]
         self.assertEqual(method.name, "method")
         self.assertEqual([n.name for n in method.args.args], ["self"])
-        self.assertEqual(method.doc, "method\n        test")
+        with pytest.warns(DeprecationWarning) as records:
+            self.assertEqual(method.doc, "method\n        test")
+            assert len(records) == 1
+        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
@@ -790,5 +948,67 @@ def test_parse_module_with_invalid_type_comments_does_not_crash():
     assert isinstance(node, nodes.Module)
 
 
+class HermeticInterpreterTest(unittest.TestCase):
+    """Modeled on https://github.com/PyCQA/astroid/pull/1207#issuecomment-951455588"""
+
+    @classmethod
+    def setUpClass(cls):
+        """Simulate a hermetic interpreter environment having no code on the filesystem."""
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            sys.path.append(tmp_dir)
+
+            # Write a python file and compile it to .pyc
+            # To make this test have even more value, we would need to come up with some
+            # code that gets inferred differently when we get its "partial representation".
+            # This code is too simple for that. But we can't use builtins either, because we would
+            # have to delete builtins from the filesystem.  But even if we engineered that,
+            # the difference might evaporate over time as inference changes.
+            cls.code_snippet = "def func():  return 42"
+            with tempfile.NamedTemporaryFile(
+                mode="w", dir=tmp_dir, suffix=".py", delete=False
+            ) as tmp:
+                tmp.write(cls.code_snippet)
+                pyc_file = py_compile.compile(tmp.name)
+                cls.pyc_name = tmp.name.replace(".py", ".pyc")
+            os.remove(tmp.name)
+            os.rename(pyc_file, cls.pyc_name)
+
+            # Import the module
+            cls.imported_module_path = pathlib.Path(cls.pyc_name)
+            cls.imported_module = importlib.import_module(cls.imported_module_path.stem)
+
+            # Delete source code from module object, filesystem, and path
+            del cls.imported_module.__file__
+            os.remove(cls.imported_module_path)
+            sys.path.remove(tmp_dir)
+
+    def test_build_from_live_module_without_source_file(self) -> None:
+        """Assert that inspect_build() is not called.
+        See comment in module_build() before the call to inspect_build():
+            "get a partial representation by introspection"
+
+        This "partial representation" was presumably causing unexpected behavior.
+        """
+        # Sanity check
+        self.assertIsNone(
+            self.imported_module.__loader__.get_source(self.imported_module_path.stem)
+        )
+        with self.assertRaises(AttributeError):
+            _ = self.imported_module.__file__
+
+        my_builder = builder.AstroidBuilder()
+        with unittest.mock.patch.object(
+            self.imported_module.__loader__,
+            "get_source",
+            return_value=self.code_snippet,
+        ):
+            with unittest.mock.patch.object(
+                my_builder, "inspect_build", side_effect=AssertionError
+            ):
+                my_builder.module_build(
+                    self.imported_module, modname=self.imported_module_path.stem
+                )
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/unittest_decorators.py b/tests/unittest_decorators.py
index 4672f87..eca20b2 100644
--- a/tests/unittest_decorators.py
+++ b/tests/unittest_decorators.py
@@ -1,7 +1,12 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import pytest
 from _pytest.recwarn import WarningsRecorder
 
-from astroid.decorators import deprecate_default_argument_values
+from astroid.const import PY38_PLUS
+from astroid.decorators import cachedproperty, deprecate_default_argument_values
 
 
 class SomeClass:
@@ -97,3 +102,18 @@ class TestDeprecationDecorators:
         instance = SomeClass(name="some_name")
         instance.func(name="", var=42)
         assert len(recwarn) == 0
+
+
+@pytest.mark.skipif(not PY38_PLUS, reason="Requires Python 3.8 or higher")
+def test_deprecation_warning_on_cachedproperty() -> None:
+    """Check the DeprecationWarning on cachedproperty."""
+
+    with pytest.warns(DeprecationWarning) as records:
+
+        class MyClass:  # pylint: disable=unused-variable
+            @cachedproperty
+            def my_property(self):
+                return 1
+
+        assert len(records) == 1
+        assert "functools.cached_property" in records[0].message.args[0]
diff --git a/tests/unittest_helpers.py b/tests/unittest_helpers.py
index 44b8813..086976d 100644
--- a/tests/unittest_helpers.py
+++ b/tests/unittest_helpers.py
@@ -1,14 +1,6 @@
-# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 hippo91 <guillaume.peillex@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import builtins
 import unittest
diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py
index 0133c78..b5fc7d9 100644
--- a/tests/unittest_inference.py
+++ b/tests/unittest_inference.py
@@ -1,53 +1,14 @@
-# Copyright (c) 2006-2015 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2007 Marien Zwart <marienz@gentoo.org>
-# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2021 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Dmitry Pribysh <dmand@yandex.ru>
-# Copyright (c) 2015 Rene Zhang <rz99@cornell.edu>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 Calen Pennington <cale@edx.org>
-# Copyright (c) 2017 Calen Pennington <calen.pennington@gmail.com>
-# Copyright (c) 2017 David Euresti <david@dropbox.com>
-# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Daniel Martin <daniel.martin@crowdstrike.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2019, 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2019 Stanislav Levin <slev@altlinux.org>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2020 Peter Kolbus <peter.kolbus@gmail.com>
-# Copyright (c) 2020 Karthikeyan Singaravelan <tir.karthi@gmail.com>
-# Copyright (c) 2020 Bryce Guinta <bryce.guinta@protonmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 Kian Meng, Ang <kianmeng.ang@gmail.com>
-# Copyright (c) 2021 Jacob Walls <jacobtylerwalls@gmail.com>
-# Copyright (c) 2021 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2021 Dmitry Shachnev <mitya57@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-# Copyright (c) 2021 doranid <ddandd@gmail.com>
-# Copyright (c) 2021 Francis Charette Migneault <francis.charette.migneault@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """Tests for the astroid inference capabilities"""
 
-import platform
 import textwrap
 import unittest
 from abc import ABCMeta
 from functools import partial
+from pathlib import Path
 from typing import Any, Callable, Dict, List, Tuple, Union
 from unittest.mock import patch
 
@@ -59,7 +20,7 @@ from astroid import helpers, nodes, objects, test_utils, util
 from astroid.arguments import CallSite
 from astroid.bases import BoundMethod, Instance, UnboundMethod
 from astroid.builder import AstroidBuilder, extract_node, parse
-from astroid.const import PY38_PLUS, PY39_PLUS
+from astroid.const import IS_PYPY, PY38_PLUS, PY39_PLUS
 from astroid.context import InferenceContext
 from astroid.exceptions import (
     AstroidTypeError,
@@ -88,6 +49,7 @@ builder = AstroidBuilder()
 
 EXC_MODULE = "builtins"
 BOOL_SPECIAL_METHOD = "__bool__"
+DATA_DIR = Path(__file__).parent / "testdata" / "python3" / "data"
 
 
 class InferenceUtilsTest(unittest.TestCase):
@@ -855,7 +817,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
         self.assertIsInstance(inferred[0], nodes.FunctionDef)
         self.assertEqual(inferred[0].name, "open")
 
-    if platform.python_implementation() == "PyPy":
+    if IS_PYPY:
         test_builtin_open = unittest.expectedFailure(test_builtin_open)
 
     def test_callfunc_context_func(self) -> None:
@@ -1732,8 +1694,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
         """
         ast = extract_node(code, __name__)
         expr = ast.func.expr
-        with pytest.raises(InferenceError):
-            next(expr.infer())
+        self.assertIs(next(expr.infer()), util.Uninferable)
 
     def test_tuple_builtin_inference(self) -> None:
         code = """
@@ -3008,6 +2969,34 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase):
         for node in ast_nodes:
             self.assertEqual(next(node.infer()), util.Uninferable)
 
+    def test_binop_self_in_list(self) -> None:
+        """If 'self' is referenced within a list it should not be bound by it.
+
+        Reported in https://github.com/PyCQA/pylint/issues/4826.
+        """
+        ast_nodes = extract_node(
+            """
+        class A:
+            def __init__(self):
+                for a in [self] + []:
+                    print(a) #@
+
+        class B:
+            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)
+        self.assertEqual(inferred_a[0]._proxied.name, "A")
+
+        inferred_b = list(ast_nodes[1].args[0].infer())
+        self.assertEqual(len(inferred_b), 1)
+        self.assertIsInstance(inferred_b[0], Instance)
+        self.assertEqual(inferred_b[0]._proxied.name, "B")
+
     def test_metaclass__getitem__(self) -> None:
         ast_node = extract_node(
             """
@@ -4399,7 +4388,8 @@ class HasattrTest(unittest.TestCase):
         """
         )
         inferred = next(node.infer())
-        self.assertEqual(inferred, util.Uninferable)
+        self.assertIsInstance(inferred, nodes.Const)
+        self.assertIs(inferred.value, False)
 
 
 class BoolOpTest(unittest.TestCase):
@@ -6116,6 +6106,26 @@ def test_property_callable_inference() -> None:
     assert inferred.value == 42
 
 
+def test_property_docstring() -> None:
+    code = """
+    class A:
+        @property
+        def test(self):
+            '''Docstring'''
+            return 42
+
+    A.test #@
+    """
+    node = extract_node(code)
+    inferred = next(node.infer())
+    assert isinstance(inferred, objects.Property)
+    assert isinstance(inferred.doc_node, nodes.Const)
+    assert inferred.doc_node.value == "Docstring"
+    with pytest.warns(DeprecationWarning) as records:
+        assert inferred.doc == "Docstring"
+        assert len(records) == 1
+
+
 def test_recursion_error_inferring_builtin_containers() -> None:
     node = extract_node(
         """
@@ -6584,5 +6594,57 @@ def test_relative_imports_init_package() -> None:
     )
 
 
+def test_inference_of_items_on_module_dict() -> None:
+    """Crash test for the inference of items() on a module's dict attribute.
+
+    Originally reported in https://github.com/PyCQA/astroid/issues/1085
+    """
+    builder.file_build(str(DATA_DIR / "module_dict_items_call" / "test.py"), "models")
+
+
+def test_recursion_on_inference_tip() -> None:
+    """Regression test for recursion in inference tip.
+
+    Originally reported in https://github.com/PyCQA/pylint/issues/5408.
+    """
+    code = """
+    class MyInnerClass:
+        ...
+
+
+    class MySubClass:
+        inner_class = MyInnerClass
+
+
+    class MyClass:
+        sub_class = MySubClass()
+
+
+    def get_unpatched_class(cls):
+        return cls
+
+
+    def get_unpatched(item):
+        lookup = get_unpatched_class if isinstance(item, type) else lambda item: None
+        return lookup(item)
+
+
+    _Child = get_unpatched(MyClass.sub_class.inner_class)
+
+
+    class Child(_Child):
+        def patch(cls):
+            MyClass.sub_class.inner_class = cls
+    """
+    module = parse(code)
+    assert module
+
+
+def test_function_def_cached_generator() -> None:
+    """Regression test for https://github.com/PyCQA/astroid/issues/817."""
+    funcdef: nodes.FunctionDef = extract_node("def func(): pass")
+    next(funcdef._infer())
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/unittest_inference_calls.py b/tests/unittest_inference_calls.py
index bb487d0..61f57eb 100644
--- a/tests/unittest_inference_calls.py
+++ b/tests/unittest_inference_calls.py
@@ -1,3 +1,7 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 """Tests for function call inference"""
 
 from astroid import bases, builder, nodes
@@ -518,8 +522,8 @@ def test_instance_method_inherited() -> None:
     A.method(B())  #@
     """
     )
-    expected = ["A", "A", "B", "B", "B"]
-    for node, expected in zip(nodes_, expected):
+    expected_names = ["A", "A", "B", "B", "B"]
+    for node, expected in zip(nodes_, expected_names):
         assert isinstance(node, nodes.NodeNG)
         inferred = node.inferred()
         assert len(inferred) == 1
@@ -549,8 +553,8 @@ def test_class_method_inherited() -> None:
     B.method()  #@
     """
     )
-    expected = ["A", "A", "B", "B"]
-    for node, expected in zip(nodes_, expected):
+    expected_names = ["A", "A", "B", "B"]
+    for node, expected in zip(nodes_, expected_names):
         assert isinstance(node, nodes.NodeNG)
         inferred = node.inferred()
         assert len(inferred) == 1
diff --git a/tests/unittest_lookup.py b/tests/unittest_lookup.py
index 1555603..1cb2526 100644
--- a/tests/unittest_lookup.py
+++ b/tests/unittest_lookup.py
@@ -1,18 +1,6 @@
-# Copyright (c) 2007-2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2010 Daniel Harding <dharding@gmail.com>
-# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 David Liu <david@cs.toronto.edu>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """tests for the astroid variable lookup capabilities
 """
@@ -394,6 +382,7 @@ class LookupTest(resources.SysPathSetup, unittest.TestCase):
         self.assertEqual(len(intstmts), 1)
         self.assertIsInstance(intstmts[0], nodes.ClassDef)
         self.assertEqual(intstmts[0].name, "int")
+        # pylint: disable=no-member
         self.assertIs(intstmts[0], nodes.const_factory(1)._proxied)
 
     def test_decorator_arguments_lookup(self) -> None:
diff --git a/tests/unittest_manager.py b/tests/unittest_manager.py
index 2cec398..4785975 100644
--- a/tests/unittest_manager.py
+++ b/tests/unittest_manager.py
@@ -1,29 +1,8 @@
-# Copyright (c) 2006, 2009-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2013 AndroWiiid <androwiiid@gmail.com>
-# Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2017 Chris Philip <chrisp533@gmail.com>
-# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2017 ioanatia <ioanatia@users.noreply.github.com>
-# Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2020 Anubhav <35621759+anubh-v@users.noreply.github.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 grayjk <grayjk@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import os
-import platform
 import site
 import sys
 import time
@@ -35,13 +14,14 @@ import pkg_resources
 
 import astroid
 from astroid import manager, test_utils
+from astroid.const import IS_JYTHON
 from astroid.exceptions import AstroidBuildingError, AstroidImportError
 
 from . import resources
 
 
 def _get_file_from_object(obj) -> str:
-    if platform.python_implementation() == "Jython":
+    if IS_JYTHON:
         return obj.__file__.split("$py.class")[0] + ".py"
     return obj.__file__
 
diff --git a/tests/unittest_modutils.py b/tests/unittest_modutils.py
index e9e5743..3fb92a8 100644
--- a/tests/unittest_modutils.py
+++ b/tests/unittest_modutils.py
@@ -1,25 +1,6 @@
-# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2015 Radosław Ganczarek <radoslaw@ganczarek.in>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Mario Corchero <mcorcherojim@bloomberg.net>
-# Copyright (c) 2018 Mario Corchero <mariocj89@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2019 markmcclain <markmcclain@users.noreply.github.com>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 Peter Kolbus <peter.kolbus@gmail.com>
-# Copyright (c) 2021-2022 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 DudeNr33 <3929834+DudeNr33@users.noreply.github.com>
-# Copyright (c) 2021 pre-commit-ci[bot] <bot@noreply.github.com>
-# Copyright (c) 2022 Alexander Shadchin <alexandr.shadchin@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """
 unit tests for module modutils (module manipulation utilities)
diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py
index 15711c1..0268c27 100644
--- a/tests/unittest_nodes.py
+++ b/tests/unittest_nodes.py
@@ -1,38 +1,11 @@
-# Copyright (c) 2006-2007, 2009-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
-# Copyright (c) 2013-2021 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2017 rr- <rr-@sakuya.pl>
-# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2018 Serhiy Storchaka <storchaka@gmail.com>
-# Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2019-2021 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2019 Alex Hall <alex.mojaki@gmail.com>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 René Fritze <47802+renefritze@users.noreply.github.com>
-# Copyright (c) 2021 Federico Bond <federicobond@gmail.com>
-# Copyright (c) 2021 hippo91 <guillaume.peillex@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """tests for specific behaviour of astroid nodes
 """
 import copy
 import os
-import platform
 import sys
 import textwrap
 import unittest
@@ -289,12 +262,6 @@ def func(param: Tuple):
         ast = abuilder.string_build(code)
         self.assertEqual(ast.as_string().strip(), code.strip())
 
-    # This test is disabled on PyPy because we cannot get a release that has proper
-    # support for f-strings (we need 7.2 at least)
-    @pytest.mark.skipif(
-        platform.python_implementation() == "PyPy",
-        reason="Needs f-string support.",
-    )
     def test_f_strings(self):
         code = r'''
 a = f"{'a'}"
@@ -632,6 +599,7 @@ class CmpNodeTest(unittest.TestCase):
 class ConstNodeTest(unittest.TestCase):
     def _test(self, value: Any) -> None:
         node = nodes.const_factory(value)
+        # pylint: disable=no-member
         self.assertIsInstance(node._proxied, nodes.ClassDef)
         self.assertEqual(node._proxied.name, value.__class__.__name__)
         self.assertIs(node.value, value)
@@ -1227,7 +1195,7 @@ def test_type_comments_with() -> None:
         """
     with a as b: # type: int
         pass
-    with a as b: # type: ignore
+    with a as b: # type: ignore[name-defined]
         pass
     """
     )
@@ -1244,7 +1212,7 @@ def test_type_comments_for() -> None:
         """
     for a, b in [1, 2, 3]: # type: List[int]
         pass
-    for a, b in [1, 2, 3]: # type: ignore
+    for a, b in [1, 2, 3]: # type: ignore[name-defined]
         pass
     """
     )
@@ -1261,7 +1229,7 @@ def test_type_coments_assign() -> None:
     module = builder.parse(
         """
     a, b = [1, 2, 3] # type: List[int]
-    a, b = [1, 2, 3] # type: ignore
+    a, b = [1, 2, 3] # type: ignore[name-defined]
     """
     )
     node = module.body[0]
@@ -1595,23 +1563,36 @@ def test_assignment_expression_in_functiondef() -> None:
 
 
 def test_get_doc() -> None:
-    node = astroid.extract_node(
-        """
+    code = textwrap.dedent(
+        """\
     def func():
         "Docstring"
         return 1
     """
     )
-    assert node.doc == "Docstring"
-
-    node = astroid.extract_node(
-        """
+    node: nodes.FunctionDef = astroid.extract_node(code)  # type: ignore[assignment]
+    with pytest.warns(DeprecationWarning) as records:
+        assert node.doc == "Docstring"
+        assert len(records) == 1
+    assert isinstance(node.doc_node, nodes.Const)
+    assert node.doc_node.value == "Docstring"
+    assert node.doc_node.lineno == 2
+    assert node.doc_node.col_offset == 4
+    assert node.doc_node.end_lineno == 2
+    assert node.doc_node.end_col_offset == 15
+
+    code = textwrap.dedent(
+        """\
     def func():
         ...
         return 1
     """
     )
-    assert node.doc is None
+    node = astroid.extract_node(code)
+    with pytest.warns(DeprecationWarning) as records:
+        assert node.doc is None
+        assert len(records) == 1
+    assert node.doc_node is None
 
 
 @test_utils.require_version(minver="3.8")
diff --git a/tests/unittest_nodes_lineno.py b/tests/unittest_nodes_lineno.py
index 73cf020..c1c089a 100644
--- a/tests/unittest_nodes_lineno.py
+++ b/tests/unittest_nodes_lineno.py
@@ -1,14 +1,19 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
 import textwrap
 
 import pytest
 
 import astroid
 from astroid import builder, nodes
-from astroid.const import PY38_PLUS, PY39_PLUS, PY310_PLUS
+from astroid.const import IS_PYPY, PY38, PY38_PLUS, PY39_PLUS, PY310_PLUS
 
 
 @pytest.mark.skipif(
-    PY38_PLUS, reason="end_lineno and end_col_offset were added in PY38"
+    PY38_PLUS and not (PY38 and IS_PYPY),
+    reason="end_lineno and end_col_offset were added in PY38",
 )
 class TestEndLinenoNotSet:
     """Test 'end_lineno' and 'end_col_offset' are initialized as 'None' for Python < 3.8."""
@@ -36,7 +41,8 @@ class TestEndLinenoNotSet:
 
 
 @pytest.mark.skipif(
-    not PY38_PLUS, reason="end_lineno and end_col_offset were added in PY38"
+    not PY38_PLUS or PY38 and IS_PYPY,
+    reason="end_lineno and end_col_offset were added in PY38",
 )
 class TestLinenoColOffset:
     """Test 'lineno', 'col_offset', 'end_lineno', and 'end_col_offset' for all nodes."""
@@ -784,7 +790,7 @@ class TestLinenoColOffset:
         assert (t3.lineno, t3.col_offset) == (10, 0)
         assert (t3.end_lineno, t3.end_col_offset) == (17, 8)
         assert (t3.body[0].lineno, t3.body[0].col_offset) == (10, 0)
-        assert (t3.body[0].end_lineno, t3.body[0].end_col_offset) == (17, 8)
+        assert (t3.body[0].end_lineno, t3.body[0].end_col_offset) == (15, 8)
         assert (t3.finalbody[0].lineno, t3.finalbody[0].col_offset) == (17, 4)
         assert (t3.finalbody[0].end_lineno, t3.finalbody[0].end_col_offset) == (17, 8)
 
@@ -1151,7 +1157,7 @@ class TestLinenoColOffset:
         c1 = ast_nodes[0]
         assert isinstance(c1, nodes.ListComp)
         assert isinstance(c1.elt, nodes.Name)
-        assert isinstance(c1.generators[0], nodes.Comprehension)  # type: ignore
+        assert isinstance(c1.generators[0], nodes.Comprehension)  # type: ignore[index]
         assert (c1.lineno, c1.col_offset) == (1, 0)
         assert (c1.end_lineno, c1.end_col_offset) == (1, 16)
         assert (c1.elt.lineno, c1.elt.col_offset) == (1, 1)
@@ -1160,7 +1166,7 @@ class TestLinenoColOffset:
         c2 = ast_nodes[1]
         assert isinstance(c2, nodes.SetComp)
         assert isinstance(c2.elt, nodes.Name)
-        assert isinstance(c2.generators[0], nodes.Comprehension)  # type: ignore
+        assert isinstance(c2.generators[0], nodes.Comprehension)  # type: ignore[index]
         assert (c2.lineno, c2.col_offset) == (2, 0)
         assert (c2.end_lineno, c2.end_col_offset) == (2, 16)
         assert (c2.elt.lineno, c2.elt.col_offset) == (2, 1)
@@ -1170,7 +1176,7 @@ class TestLinenoColOffset:
         assert isinstance(c3, nodes.DictComp)
         assert isinstance(c3.key, nodes.Name)
         assert isinstance(c3.value, nodes.Name)
-        assert isinstance(c3.generators[0], nodes.Comprehension)  # type: ignore
+        assert isinstance(c3.generators[0], nodes.Comprehension)  # type: ignore[index]
         assert (c3.lineno, c3.col_offset) == (3, 0)
         assert (c3.end_lineno, c3.end_col_offset) == (3, 22)
         assert (c3.key.lineno, c3.key.col_offset) == (3, 1)
@@ -1181,7 +1187,7 @@ class TestLinenoColOffset:
         c4 = ast_nodes[3]
         assert isinstance(c4, nodes.GeneratorExp)
         assert isinstance(c4.elt, nodes.Name)
-        assert isinstance(c4.generators[0], nodes.Comprehension)  # type: ignore
+        assert isinstance(c4.generators[0], nodes.Comprehension)  # type: ignore[index]
         assert (c4.lineno, c4.col_offset) == (4, 0)
         assert (c4.end_lineno, c4.end_col_offset) == (4, 16)
         assert (c4.elt.lineno, c4.elt.col_offset) == (4, 1)
diff --git a/tests/unittest_nodes_position.py b/tests/unittest_nodes_position.py
new file mode 100644
index 0000000..92df636
--- /dev/null
+++ b/tests/unittest_nodes_position.py
@@ -0,0 +1,164 @@
+# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
+# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
+
+import textwrap
+from typing import List
+
+from astroid import builder, nodes
+
+
+class TestNodePosition:
+    """Test node ``position`` attribute."""
+
+    @staticmethod
+    def test_position_class() -> None:
+        """Position should only include keyword and name.
+
+        >>> class A(Parent):
+        >>> ^^^^^^^
+        """
+        code = textwrap.dedent(
+            """
+        class A:  #@
+            ...
+
+        class B(A):  #@
+            pass
+
+        class C:  #@
+            '''Docstring'''
+
+            class D:  #@
+                ...
+
+        class E:  #@
+            def f():
+                ...
+
+        @decorator
+        class F:  #@
+            ...
+        """
+        ).strip()
+        ast_nodes: List[nodes.NodeNG] = builder.extract_node(code)  # type: ignore[assignment]
+
+        a = ast_nodes[0]
+        assert isinstance(a, nodes.ClassDef)
+        assert a.position == (1, 0, 1, 7)
+
+        b = ast_nodes[1]
+        assert isinstance(b, nodes.ClassDef)
+        assert b.position == (4, 0, 4, 7)
+
+        c = ast_nodes[2]
+        assert isinstance(c, nodes.ClassDef)
+        assert c.position == (7, 0, 7, 7)
+
+        d = ast_nodes[3]
+        assert isinstance(d, nodes.ClassDef)
+        assert d.position == (10, 4, 10, 11)
+
+        e = ast_nodes[4]
+        assert isinstance(e, nodes.ClassDef)
+        assert e.position == (13, 0, 13, 7)
+
+        f = ast_nodes[5]
+        assert isinstance(f, nodes.ClassDef)
+        assert f.position == (18, 0, 18, 7)
+
+    @staticmethod
+    def test_position_function() -> None:
+        """Position should only include keyword and name.
+
+        >>> def func(var: int = 42):
+        >>> ^^^^^^^^
+        """
+        code = textwrap.dedent(
+            """
+        def a():  #@
+            ...
+
+        def b():  #@
+            '''Docstring'''
+
+        def c(  #@
+            var: int = 42
+        ):
+            def d():  #@
+                ...
+
+        @decorator
+        def e():  #@
+            ...
+        """
+        ).strip()
+        ast_nodes: List[nodes.NodeNG] = builder.extract_node(code)  # type: ignore[assignment]
+
+        a = ast_nodes[0]
+        assert isinstance(a, nodes.FunctionDef)
+        assert a.position == (1, 0, 1, 5)
+
+        b = ast_nodes[1]
+        assert isinstance(b, nodes.FunctionDef)
+        assert b.position == (4, 0, 4, 5)
+
+        c = ast_nodes[2]
+        assert isinstance(c, nodes.FunctionDef)
+        assert c.position == (7, 0, 7, 5)
+
+        d = ast_nodes[3]
+        assert isinstance(d, nodes.FunctionDef)
+        assert d.position == (10, 4, 10, 9)
+
+        e = ast_nodes[4]
+        assert isinstance(e, nodes.FunctionDef)
+        assert e.position == (14, 0, 14, 5)
+
+    @staticmethod
+    def test_position_async_function() -> None:
+        """Position should only include keyword and name.
+
+        >>> async def func(var: int = 42):
+        >>> ^^^^^^^^^^^^^^
+        """
+        code = textwrap.dedent(
+            """
+        async def a():  #@
+            ...
+
+        async def b():  #@
+            '''Docstring'''
+
+        async def c(  #@
+            var: int = 42
+        ):
+            async def d():  #@
+                ...
+
+        @decorator
+        async def e():  #@
+            ...
+        """
+        ).strip()
+        ast_nodes: List[nodes.NodeNG] = builder.extract_node(code)  # type: ignore[assignment]
+
+        a = ast_nodes[0]
+        assert isinstance(a, nodes.FunctionDef)
+        assert a.position == (1, 0, 1, 11)
+
+        b = ast_nodes[1]
+        assert isinstance(b, nodes.FunctionDef)
+        assert b.position == (4, 0, 4, 11)
+
+        c = ast_nodes[2]
+        assert isinstance(c, nodes.FunctionDef)
+        assert c.position == (7, 0, 7, 11)
+
+        d = ast_nodes[3]
+        assert isinstance(d, nodes.FunctionDef)
+        assert d.position == (10, 4, 10, 15)
+
+        e = ast_nodes[4]
+        assert isinstance(e, nodes.FunctionDef)
+        assert e.position == (14, 0, 14, 11)
diff --git a/tests/unittest_object_model.py b/tests/unittest_object_model.py
index 81513d3..a9f9f1f 100644
--- a/tests/unittest_object_model.py
+++ b/tests/unittest_object_model.py
@@ -1,16 +1,6 @@
-# Copyright (c) 2016-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Keichi Takahashi <keichi.t@me.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-# Copyright (c) 2021 hippo91 <guillaume.peillex@gmail.com>
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import unittest
 import xml
diff --git a/tests/unittest_objects.py b/tests/unittest_objects.py
index a792dc7..662128e 100644
--- a/tests/unittest_objects.py
+++ b/tests/unittest_objects.py
@@ -1,15 +1,6 @@
-# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 hippo91 <guillaume.peillex@gmail.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import unittest
 from typing import List
diff --git a/tests/unittest_protocols.py b/tests/unittest_protocols.py
index dedf533..7c3abb8 100644
--- a/tests/unittest_protocols.py
+++ b/tests/unittest_protocols.py
@@ -1,19 +1,6 @@
-# Copyright (c) 2015-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Kian Meng, Ang <kianmeng.ang@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import contextlib
 import unittest
diff --git a/tests/unittest_python3.py b/tests/unittest_python3.py
index 7534316..9f60833 100644
--- a/tests/unittest_python3.py
+++ b/tests/unittest_python3.py
@@ -1,19 +1,6 @@
-# Copyright (c) 2010, 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2012 FELD Boris <lothiraldan@gmail.com>
-# Copyright (c) 2013-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Jared Garst <jgarst@users.noreply.github.com>
-# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017 Hugo <hugovk@users.noreply.github.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import unittest
 from textwrap import dedent
diff --git a/tests/unittest_raw_building.py b/tests/unittest_raw_building.py
index dececbc..387a3f4 100644
--- a/tests/unittest_raw_building.py
+++ b/tests/unittest_raw_building.py
@@ -1,24 +1,14 @@
-# Copyright (c) 2013 AndroWiiid <androwiiid@gmail.com>
-# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
-import platform
 import unittest
 
 import _io
+import pytest
 
 from astroid.builder import AstroidBuilder
+from astroid.const import IS_PYPY
 from astroid.raw_building import (
     attach_dummy_node,
     build_class,
@@ -44,12 +34,18 @@ class RawBuildingTC(unittest.TestCase):
     def test_build_class(self) -> None:
         node = build_class("MyClass")
         self.assertEqual(node.name, "MyClass")
-        self.assertEqual(node.doc, None)
+        with pytest.warns(DeprecationWarning) as records:
+            self.assertEqual(node.doc, None)
+            assert len(records) == 1
+        self.assertEqual(node.doc_node, None)
 
     def test_build_function(self) -> None:
         node = build_function("MyFunction")
         self.assertEqual(node.name, "MyFunction")
-        self.assertEqual(node.doc, None)
+        with pytest.warns(DeprecationWarning) as records:
+            self.assertEqual(node.doc, None)
+            assert len(records) == 1
+        self.assertEqual(node.doc_node, None)
 
     def test_build_function_args(self) -> None:
         args = ["myArgs1", "myArgs2"]
@@ -78,7 +74,7 @@ class RawBuildingTC(unittest.TestCase):
         node = build_from_import("astroid", names)
         self.assertEqual(len(names), len(node.names))
 
-    @unittest.skipIf(platform.python_implementation() == "PyPy", "Only affects CPython")
+    @unittest.skipIf(IS_PYPY, "Only affects CPython")
     def test_io_is__io(self):
         # _io module calls itself io. This leads
         # to cyclic dependencies when astroid tries to resolve
diff --git a/tests/unittest_regrtest.py b/tests/unittest_regrtest.py
index c7321dc..2ca8a45 100644
--- a/tests/unittest_regrtest.py
+++ b/tests/unittest_regrtest.py
@@ -1,22 +1,6 @@
-# Copyright (c) 2006-2008, 2010-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2007 Marien Zwart <marienz@gentoo.org>
-# Copyright (c) 2013-2014 Google, Inc.
-# Copyright (c) 2014-2016, 2018-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2019, 2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import sys
 import textwrap
@@ -24,11 +8,13 @@ import unittest
 
 import pytest
 
-from astroid import MANAGER, Instance, nodes, parse, test_utils
+from astroid import MANAGER, Instance, bases, nodes, parse, test_utils
 from astroid.builder import AstroidBuilder, extract_node
 from astroid.const import PY38_PLUS
+from astroid.context import InferenceContext
 from astroid.exceptions import InferenceError
 from astroid.raw_building import build_module
+from astroid.util import Uninferable
 
 from . import resources
 
@@ -399,5 +385,33 @@ def test_regression_crash_classmethod() -> None:
     parse(code)
 
 
+def test_max_inferred_for_complicated_class_hierarchy() -> None:
+    """Regression test for a crash reported in https://github.com/PyCQA/pylint/issues/5679.
+
+    The class hierarchy of 'sqlalchemy' is so intricate that it becomes uninferable with
+    the standard max_inferred of 100. We used to crash when this happened.
+    """
+    # Create module and get relevant nodes
+    module = resources.build_file(
+        str(resources.RESOURCE_PATH / "max_inferable_limit_for_classes" / "main.py")
+    )
+    init_attr_node = module.body[-1].body[0].body[0].value.func
+    init_object_node = module.body[-1].mro()[-1]["__init__"]
+    super_node = next(init_attr_node.expr.infer())
+
+    # Arbitrarily limit the max number of infered nodes per context
+    InferenceContext.max_inferred = -1
+    context = InferenceContext()
+
+    # Try to infer 'object.__init__' > because of limit is impossible
+    for inferred in bases._infer_stmts([init_object_node], context, frame=super):
+        assert inferred == Uninferable
+
+    # Reset inference limit
+    InferenceContext.max_inferred = 100
+    # Check that we don't crash on a previously uninferable node
+    assert super_node.getattr("__init__", context=context)[0] == Uninferable
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/unittest_scoped_nodes.py b/tests/unittest_scoped_nodes.py
index d2db170..6d1652e 100644
--- a/tests/unittest_scoped_nodes.py
+++ b/tests/unittest_scoped_nodes.py
@@ -1,34 +1,6 @@
-# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2011, 2013-2015 Google, Inc.
-# Copyright (c) 2013-2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2013 Phil Schaf <flying-sheep@web.de>
-# Copyright (c) 2014 Eevee (Alex Munroe) <amunroe@yelp.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2015 Rene Zhang <rz99@cornell.edu>
-# Copyright (c) 2015 Florian Bruhin <me@the-compiler.org>
-# Copyright (c) 2015 Philip Lorenz <philip@bithub.de>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2017, 2019 Łukasz Rogalski <rogalski.91@gmail.com>
-# Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2017 Derek Gustafson <degustaf@gmail.com>
-# Copyright (c) 2018-2019 Ville Skyttä <ville.skytta@iki.fi>
-# Copyright (c) 2018 brendanator <brendan.maginnis@gmail.com>
-# Copyright (c) 2018 Anthony Sottile <asottile@umich.edu>
-# Copyright (c) 2019-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
-# Copyright (c) 2019 Peter de Blanc <peter@standard.ai>
-# Copyright (c) 2020 David Gilman <davidgilman1@gmail.com>
-# Copyright (c) 2020 Tim Martin <tim@asymptotic.co.uk>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com>
-# Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
-# Copyright (c) 2021 doranid <ddandd@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-# Copyright (c) 2021 Andrew Haigh <hello@nelf.in>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 """tests for specific behaviour of astroid scoped nodes (i.e. module, class and
 function)
@@ -38,14 +10,24 @@ import os
 import sys
 import textwrap
 import unittest
+import warnings
 from functools import partial
 from typing import Any, List, Union
 
 import pytest
 
-from astroid import MANAGER, builder, nodes, objects, test_utils, util
+from astroid import (
+    MANAGER,
+    builder,
+    extract_node,
+    nodes,
+    objects,
+    parse,
+    test_utils,
+    util,
+)
 from astroid.bases import BoundMethod, Generator, Instance, UnboundMethod
-from astroid.const import PY38_PLUS
+from astroid.const import IS_PYPY, PY38, PY38_PLUS, PY310_PLUS, WIN32
 from astroid.exceptions import (
     AttributeInferenceError,
     DuplicateBasesError,
@@ -202,8 +184,7 @@ class ModuleNodeTest(ModuleLoader, unittest.TestCase):
 
     def test_relative_to_absolute_name(self) -> None:
         # package
-        mod = nodes.Module("very.multi.package", "doc")
-        mod.package = True
+        mod = nodes.Module("very.multi.package", package=True)
         modname = mod.relative_to_absolute_name("utils", 1)
         self.assertEqual(modname, "very.multi.package.utils")
         modname = mod.relative_to_absolute_name("utils", 2)
@@ -213,8 +194,7 @@ class ModuleNodeTest(ModuleLoader, unittest.TestCase):
         modname = mod.relative_to_absolute_name("", 1)
         self.assertEqual(modname, "very.multi.package")
         # non package
-        mod = nodes.Module("very.multi.module", "doc")
-        mod.package = False
+        mod = nodes.Module("very.multi.module", package=False)
         modname = mod.relative_to_absolute_name("utils", 0)
         self.assertEqual(modname, "very.multi.utils")
         modname = mod.relative_to_absolute_name("utils", 1)
@@ -225,8 +205,7 @@ class ModuleNodeTest(ModuleLoader, unittest.TestCase):
         self.assertEqual(modname, "very.multi")
 
     def test_relative_to_absolute_name_beyond_top_level(self) -> None:
-        mod = nodes.Module("a.b.c", "")
-        mod.package = True
+        mod = nodes.Module("a.b.c", package=True)
         for level in (5, 4):
             with self.assertRaises(TooManyLevelsError) as cm:
                 mod.relative_to_absolute_name("test", level)
@@ -280,7 +259,7 @@ class ModuleNodeTest(ModuleLoader, unittest.TestCase):
         path = resources.find("data/all.py")
         file_build = builder.AstroidBuilder().file_build(path, "all")
         with self.assertRaises(AttributeError):
-            # pylint: disable=pointless-statement
+            # pylint: disable=pointless-statement, no-member
             file_build.file_stream
 
     def test_stream_api(self) -> None:
@@ -292,6 +271,69 @@ class ModuleNodeTest(ModuleLoader, unittest.TestCase):
             with open(path, "rb") as file_io:
                 self.assertEqual(stream.read(), file_io.read())
 
+    @staticmethod
+    def test_singleline_docstring() -> None:
+        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
+        assert module.doc_node.col_offset == 0
+        assert module.doc_node.end_lineno == 1
+        assert module.doc_node.end_col_offset == 17
+
+    @staticmethod
+    def test_multiline_docstring() -> None:
+        data = textwrap.dedent(
+            """\
+            '''Hello World
+
+            Also on this line.
+            '''
+            foo = 1
+        """
+        )
+        module = builder.parse(data, __name__)
+
+        assert isinstance(module.doc_node, nodes.Const)
+        assert module.doc_node.lineno == 1
+        assert module.doc_node.col_offset == 0
+        assert module.doc_node.end_lineno == 4
+        assert module.doc_node.end_col_offset == 3
+
+    @staticmethod
+    def test_comment_before_docstring() -> None:
+        data = textwrap.dedent(
+            """\
+            # Some comment
+            '''This is
+
+            a multiline docstring.
+            '''
+        """
+        )
+        module = builder.parse(data, __name__)
+
+        assert isinstance(module.doc_node, nodes.Const)
+        assert module.doc_node.lineno == 2
+        assert module.doc_node.col_offset == 0
+        assert module.doc_node.end_lineno == 5
+        assert module.doc_node.end_col_offset == 3
+
+    @staticmethod
+    def test_without_docstring() -> None:
+        data = textwrap.dedent(
+            """\
+            foo = 1
+        """
+        )
+        module = builder.parse(data, __name__)
+        assert module.doc_node is None
+
 
 class FunctionNodeTest(ModuleLoader, unittest.TestCase):
     def test_special_attributes(self) -> None:
@@ -442,6 +484,12 @@ class FunctionNodeTest(ModuleLoader, unittest.TestCase):
         astroid = builder.parse("lmbd = lambda: None", __name__)
         self.assertEqual(f"{__name__}.<lambda>", astroid["lmbd"].parent.value.qname())
 
+    def test_lambda_getattr(self) -> None:
+        astroid = builder.parse("lmbd = lambda: None")
+        self.assertIsInstance(
+            astroid["lmbd"].parent.value.getattr("__code__")[0], nodes.Unknown
+        )
+
     def test_is_method(self) -> None:
         data = """
             class A:
@@ -473,6 +521,20 @@ class FunctionNodeTest(ModuleLoader, unittest.TestCase):
         astroid = builder.parse(code, __name__)
         self.assertEqual(astroid["f"].argnames(), ["a", "b", "c", "args", "kwargs"])
 
+        code_with_kwonly_args = "def f(a, b, *args, c=None, d=None, **kwargs): pass"
+        astroid = builder.parse(code_with_kwonly_args, __name__)
+        self.assertEqual(
+            astroid["f"].argnames(), ["a", "b", "args", "c", "d", "kwargs"]
+        )
+
+    @unittest.skipUnless(PY38_PLUS, "positional-only argument syntax")
+    def test_positional_only_argnames(self) -> None:
+        code = "def f(a, b, /, c=None, *args, d, **kwargs): pass"
+        astroid = builder.parse(code, __name__)
+        self.assertEqual(
+            astroid["f"].argnames(), ["a", "b", "c", "args", "d", "kwargs"]
+        )
+
     def test_return_nothing(self) -> None:
         """test inferred value on a function with empty return"""
         data = """
@@ -735,6 +797,118 @@ class FunctionNodeTest(ModuleLoader, unittest.TestCase):
         self.assertIsInstance(inferred, nodes.ClassDef)
         self.assertEqual(inferred.name, "MyClass")
 
+    @staticmethod
+    def test_singleline_docstring() -> None:
+        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)
+        assert func.doc_node.lineno == 2
+        assert func.doc_node.col_offset == 4
+        assert func.doc_node.end_lineno == 2
+        assert func.doc_node.end_col_offset == 21
+
+    @staticmethod
+    def test_multiline_docstring() -> None:
+        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)
+        assert func.doc_node.lineno == 2
+        assert func.doc_node.col_offset == 4
+        assert func.doc_node.end_lineno == 5
+        assert func.doc_node.end_col_offset == 7
+
+    @staticmethod
+    def test_multiline_docstring_async() -> None:
+        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)
+        assert func.doc_node.lineno == 2
+        assert func.doc_node.col_offset == 4
+        assert func.doc_node.end_lineno == 5
+        assert func.doc_node.end_col_offset == 7
+
+    @staticmethod
+    def test_docstring_special_cases() -> None:
+        code = textwrap.dedent(
+            """\
+        def f1(var: tuple = ()):  #@
+            'Hello World'
+
+        def f2() -> "just some comment with an open bracket(":  #@
+            'Hello World'
+
+        def f3() -> "Another comment with a colon: ":  #@
+            'Hello World'
+
+        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
+
+        assert isinstance(ast_nodes[0].doc_node, nodes.Const)
+        assert ast_nodes[0].doc_node.lineno == 2
+        assert ast_nodes[0].doc_node.col_offset == 4
+        assert ast_nodes[0].doc_node.end_lineno == 2
+        assert ast_nodes[0].doc_node.end_col_offset == 17
+
+        assert isinstance(ast_nodes[1].doc_node, nodes.Const)
+        assert ast_nodes[1].doc_node.lineno == 5
+        assert ast_nodes[1].doc_node.col_offset == 4
+        assert ast_nodes[1].doc_node.end_lineno == 5
+        assert ast_nodes[1].doc_node.end_col_offset == 17
+
+        assert isinstance(ast_nodes[2].doc_node, nodes.Const)
+        assert ast_nodes[2].doc_node.lineno == 8
+        assert ast_nodes[2].doc_node.col_offset == 4
+        assert ast_nodes[2].doc_node.end_lineno == 8
+        assert ast_nodes[2].doc_node.end_col_offset == 17
+
+        assert isinstance(ast_nodes[3].doc_node, nodes.Const)
+        assert ast_nodes[3].doc_node.lineno == 12
+        assert ast_nodes[3].doc_node.col_offset == 4
+        assert ast_nodes[3].doc_node.end_lineno == 12
+        assert ast_nodes[3].doc_node.end_col_offset == 17
+
+    @staticmethod
+    def test_without_docstring() -> None:
+        code = textwrap.dedent(
+            """\
+            def foo():
+                bar = 1
+        """
+        )
+        func: nodes.FunctionDef = builder.extract_node(code)  # type: ignore[assignment]
+        assert func.doc_node is None
+
 
 class ClassNodeTest(ModuleLoader, unittest.TestCase):
     def test_dict_interface(self) -> None:
@@ -764,7 +938,10 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
             self.assertEqual(
                 len(cls.getattr("__doc__")), 1, (cls, cls.getattr("__doc__"))
             )
-            self.assertEqual(cls.getattr("__doc__")[0].value, cls.doc)
+            with pytest.warns(DeprecationWarning) as records:
+                self.assertEqual(cls.getattr("__doc__")[0].value, cls.doc)
+                assert len(records) == 1
+            self.assertEqual(cls.getattr("__doc__")[0].value, cls.doc_node.value)
             self.assertEqual(len(cls.getattr("__module__")), 4)
             self.assertEqual(len(cls.getattr("__dict__")), 1)
             self.assertEqual(len(cls.getattr("__mro__")), 1)
@@ -1078,15 +1255,19 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
                 print(x)
 
             @f(a=2,
-               b=3)
+               b=3,
+            )
             def g2():
                 pass
         """
         astroid = builder.parse(data)
         self.assertEqual(astroid["g1"].fromlineno, 4)
         self.assertEqual(astroid["g1"].tolineno, 5)
-        self.assertEqual(astroid["g2"].fromlineno, 9)
-        self.assertEqual(astroid["g2"].tolineno, 10)
+        if not PY38_PLUS or PY38 and IS_PYPY:
+            self.assertEqual(astroid["g2"].fromlineno, 9)
+        else:
+            self.assertEqual(astroid["g2"].fromlineno, 10)
+        self.assertEqual(astroid["g2"].tolineno, 11)
 
     def test_metaclass_error(self) -> None:
         astroid = builder.parse(
@@ -1670,6 +1851,49 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
         with self.assertRaises(DuplicateBasesError):
             cls.mro()
 
+    @test_utils.require_version(minver="3.7")
+    def test_mro_typing_extensions(self):
+        """Regression test for mro() inference on typing_extesnions.
+
+        Regression reported in:
+        https://github.com/PyCQA/astroid/issues/1124
+        """
+        module = parse(
+            """
+        import abc
+        import typing
+        import dataclasses
+
+        import typing_extensions
+
+        T = typing.TypeVar("T")
+
+        class MyProtocol(typing_extensions.Protocol): pass
+        class EarlyBase(typing.Generic[T], MyProtocol): pass
+        class Base(EarlyBase[T], abc.ABC): pass
+        class Final(Base[object]): pass
+        """
+        )
+        class_names = [
+            "ABC",
+            "Base",
+            "EarlyBase",
+            "Final",
+            "Generic",
+            "MyProtocol",
+            "Protocol",
+            "object",
+        ]
+        if not PY38_PLUS:
+            class_names.pop(-2)
+        # typing_extensions is not installed on this combination of version
+        # and platform
+        if PY310_PLUS and WIN32:
+            class_names.pop(-2)
+
+        final_def = module.body[-1]
+        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(
             """
@@ -2024,6 +2248,52 @@ class ClassNodeTest(ModuleLoader, unittest.TestCase):
         # Should not crash
         builder.parse(data)
 
+    @staticmethod
+    def test_singleline_docstring() -> None:
+        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
+        assert node.doc_node.col_offset == 4
+        assert node.doc_node.end_lineno == 2
+        assert node.doc_node.end_col_offset == 21
+
+    @staticmethod
+    def test_multiline_docstring() -> None:
+        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
+        assert node.doc_node.col_offset == 4
+        assert node.doc_node.end_lineno == 5
+        assert node.doc_node.end_col_offset == 7
+
+    @staticmethod
+    def test_without_docstring() -> None:
+        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(
@@ -2352,5 +2622,85 @@ class TestFrameNodes:
         assert module.body[1].value.locals["x"][0].frame(future=True) == module
 
 
+def test_deprecation_of_doc_attribute() -> None:
+    code = textwrap.dedent(
+        """\
+    def func():
+        "Docstring"
+        return 1
+    """
+    )
+    node: nodes.FunctionDef = extract_node(code)  # type: ignore[assignment]
+    with pytest.warns(DeprecationWarning) as records:
+        assert node.doc == "Docstring"
+        assert len(records) == 1
+    with pytest.warns(DeprecationWarning) as records:
+        node.doc = None
+        assert len(records) == 1
+
+    code = textwrap.dedent(
+        """\
+    class MyClass():
+        '''Docstring'''
+    """
+    )
+    node: nodes.ClassDef = extract_node(code)  # type: ignore[assignment]
+    with pytest.warns(DeprecationWarning) as records:
+        assert node.doc == "Docstring"
+        assert len(records) == 1
+    with pytest.warns(DeprecationWarning) as records:
+        node.doc = None
+        assert len(records) == 1
+
+    code = textwrap.dedent(
+        """\
+    '''Docstring'''
+    """
+    )
+    node = parse(code)
+    with pytest.warns(DeprecationWarning) as records:
+        assert node.doc == "Docstring"
+        assert len(records) == 1
+    with pytest.warns(DeprecationWarning) as records:
+        node.doc = None
+        assert len(records) == 1
+
+    # If 'doc' isn't passed to Module, ClassDef, FunctionDef,
+    # no DeprecationWarning should be raised
+    doc_node = nodes.Const("Docstring")
+    with warnings.catch_warnings():
+        # Modify warnings filter to raise error for DeprecationWarning
+        warnings.simplefilter("error", DeprecationWarning)
+        node_module = nodes.Module(name="MyModule")
+        node_module.postinit(body=[], doc_node=doc_node)
+        assert node_module.doc_node == doc_node
+        node_class = nodes.ClassDef(name="MyClass")
+        node_class.postinit(bases=[], body=[], decorators=[], doc_node=doc_node)
+        assert node_class.doc_node == doc_node
+        node_func = nodes.FunctionDef(name="MyFunction")
+        node_func.postinit(args=nodes.Arguments(), body=[], doc_node=doc_node)
+        assert node_func.doc_node == doc_node
+
+    # Test 'doc' attribute if only 'doc_node' is passed
+    with pytest.warns(DeprecationWarning) as records:
+        assert node_module.doc == "Docstring"
+        assert len(records) == 1
+    with pytest.warns(DeprecationWarning) as records:
+        assert node_class.doc == "Docstring"
+        assert len(records) == 1
+    with pytest.warns(DeprecationWarning) as records:
+        assert node_func.doc == "Docstring"
+        assert len(records) == 1
+
+    # If 'doc' is passed to Module, ClassDef, FunctionDef,
+    # a DeprecationWarning should be raised
+    doc_node = nodes.Const("Docstring")
+    with pytest.warns(DeprecationWarning) as records:
+        node_module = nodes.Module(name="MyModule", doc="Docstring")
+        node_class = nodes.ClassDef(name="MyClass", doc="Docstring")
+        node_func = nodes.FunctionDef(name="MyFunction", doc="Docstring")
+        assert len(records) == 3
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/unittest_transforms.py b/tests/unittest_transforms.py
index 63ac10d..971eaf9 100644
--- a/tests/unittest_transforms.py
+++ b/tests/unittest_transforms.py
@@ -1,15 +1,6 @@
-# Copyright (c) 2015-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2015-2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Jakub Wilk <jwilk@jwilk.net>
-# Copyright (c) 2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
-
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import contextlib
 import time
diff --git a/tests/unittest_utils.py b/tests/unittest_utils.py
index fd2026f..1b8d712 100644
--- a/tests/unittest_utils.py
+++ b/tests/unittest_utils.py
@@ -1,15 +1,6 @@
-# Copyright (c) 2008-2010, 2013 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
-# Copyright (c) 2014 Google, Inc.
-# Copyright (c) 2015-2016, 2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
-# Copyright (c) 2016 Ceridwen <ceridwenv@gmail.com>
-# Copyright (c) 2016 Dave Baum <dbaum@google.com>
-# Copyright (c) 2019 Ashley Whetter <ashley@awhetter.co.uk>
-# Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
-# Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
-# Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
-
 # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
 # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
+# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
 
 import unittest