Merge branch 'maintenance/4.0.x' into merge-maintenance-into-main
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 4d31f71..f798cf5 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -122,7 +122,7 @@
           . venv/bin/activate
           pytest --cov
       - name: Upload coverage artifact
-        uses: &actions-upload-artifact actions/upload-artifact@v4.6.2
+        uses: &actions-upload-artifact actions/upload-artifact@v5.0.0
         with:
           name: coverage-linux-${{ matrix.python-version }}
           path: .coverage
@@ -226,7 +226,7 @@
       - name: Install dependencies
         run: pip install -U -r requirements_minimal.txt
       - name: Download all coverage artifacts
-        uses: actions/download-artifact@v5.0.0
+        uses: actions/download-artifact@v6.0.0
       - name: Combine Linux coverage results
         run: |
           coverage combine coverage-linux*/.coverage
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 9b30ac2..44b2080 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -50,7 +50,7 @@
 
       # Initializes the CodeQL tools for scanning.
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@v3
+        uses: github/codeql-action/init@v4
         with:
           languages: ${{ matrix.language }}
           # If you wish to specify custom queries, you can do so here or in a config file.
@@ -61,7 +61,7 @@
       # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
       # If this step fails, then you should remove it and run the build manually (see below)
       - name: Autobuild
-        uses: github/codeql-action/autobuild@v3
+        uses: github/codeql-action/autobuild@v4
 
       # â„šī¸ Command-line programs to run using the OS shell.
       # 📚 https://git.io/JvXDl
@@ -75,4 +75,4 @@
       #   make release
 
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v3
+        uses: github/codeql-action/analyze@v4
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2c6cbdb..8e06a63 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -34,7 +34,7 @@
         run: |
           python -m build
       - name: Upload release assets
-        uses: actions/upload-artifact@v4.6.2
+        uses: actions/upload-artifact@v5.0.0
         with:
           name: release-assets
           path: dist/
@@ -50,7 +50,7 @@
       id-token: write
     steps:
       - name: Download release assets
-        uses: actions/download-artifact@v5.0.0
+        uses: actions/download-artifact@v6.0.0
         with:
           name: release-assets
           path: dist/
@@ -67,13 +67,13 @@
       id-token: write
     steps:
       - name: Download release assets
-        uses: actions/download-artifact@v5.0.0
+        uses: actions/download-artifact@v6.0.0
         with:
           name: release-assets
           path: dist/
       - name: Sign the dists with Sigstore and upload assets to Github release
         if: github.event_name == 'release'
-        uses: sigstore/gh-action-sigstore-python@v3.0.1
+        uses: sigstore/gh-action-sigstore-python@v3.1.0
         with:
           inputs: |
             ./dist/*.tar.gz
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 70095b1..1f6f967 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -10,7 +10,7 @@
       - id: end-of-file-fixer
         exclude: tests/testdata
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: "v0.13.2"
+    rev: "v0.14.3"
     hooks:
       - id: ruff-check
         args: ["--fix"]
@@ -22,7 +22,7 @@
         exclude: tests/testdata|setup.py
         types: [python]
   - repo: https://github.com/asottile/pyupgrade
-    rev: v3.20.0
+    rev: v3.21.0
     hooks:
       - id: pyupgrade
         exclude: tests/testdata
@@ -32,7 +32,7 @@
     hooks:
       - id: black-disable-checker
         exclude: tests/test_nodes_lineno.py
-  - repo: https://github.com/psf/black
+  - repo: https://github.com/psf/black-pre-commit-mirror
     rev: 25.9.0
     hooks:
       - id: black
@@ -81,6 +81,6 @@
       - id: prettier
         args: [--prose-wrap=always, --print-width=88]
   - repo: https://github.com/tox-dev/pyproject-fmt
-    rev: "v2.6.0"
+    rev: "v2.11.0"
     hooks:
       - id: pyproject-fmt
diff --git a/ChangeLog b/ChangeLog
index 60bca91..812dea3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,22 @@
 ============================
 Release date: TBA
 
+* Make `type.__new__()` raise clear errors instead of returning `None`
+
+* Move object dunder methods from ``FunctionModel`` to ``ObjectModel`` to make them
+  available on all object types, not just functions.
+
+  Closes #2742
+  Closes #2741
+  Closes pylint-dev/pylint#6094
+
+* ``lineno`` and ``end_lineno`` are now available on ``Arguments``.
+
+* Add helper to iterate over all annotations nodes of function arguments,
+  ``Arguments.get_annotations()``.
+
+  Refs #2860
+
 
 
 What's New in astroid 4.0.3?
@@ -119,7 +135,7 @@
 
   Closes #2672
 
-* Add basic support for ``ast.TemplateStr`` and ``ast.Interpolation``added in Python 3.14.
+* Add basic support for ``ast.TemplateStr`` and ``ast.Interpolation`` added in Python 3.14.
 
   Refs #2789
 
diff --git a/astroid/__pkginfo__.py b/astroid/__pkginfo__.py
index 8ef2395..dfe71c3 100644
--- a/astroid/__pkginfo__.py
+++ b/astroid/__pkginfo__.py
@@ -2,5 +2,5 @@
 # For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
 # Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
 
-__version__ = "4.0.2"
+__version__ = "4.1.0-dev0"
 version = __version__
diff --git a/astroid/bases.py b/astroid/bases.py
index a029da6..7fb3da2 100644
--- a/astroid/bases.py
+++ b/astroid/bases.py
@@ -250,7 +250,9 @@
             values = self._proxied.instance_attr(name, context)
         except AttributeInferenceError as exc:
             if self.special_attributes and name in self.special_attributes:
-                return [self.special_attributes.lookup(name)]
+                special_attr = self.special_attributes.lookup(name)
+                if not isinstance(special_attr, nodes.Unknown):
+                    return [special_attr]
 
             if lookupclass:
                 # Class attributes not available through the instance
@@ -571,10 +573,14 @@
             raise InferenceError(context=context) from e
         if not isinstance(mcs, nodes.ClassDef):
             # Not a valid first argument.
-            return None
+            raise InferenceError(
+                "type.__new__() requires a class for metaclass", context=context
+            )
         if not mcs.is_subtype_of("builtins.type"):
             # Not a valid metaclass.
-            return None
+            raise InferenceError(
+                "type.__new__() metaclass must be a subclass of type", context=context
+            )
 
         # Verify the name
         try:
@@ -583,10 +589,14 @@
             raise InferenceError(context=context) from e
         if not isinstance(name, nodes.Const):
             # Not a valid name, needs to be a const.
-            return None
+            raise InferenceError(
+                "type.__new__() requires a constant for name", context=context
+            )
         if not isinstance(name.value, str):
             # Needs to be a string.
-            return None
+            raise InferenceError(
+                "type.__new__() requires a string for name", context=context
+            )
 
         # Verify the bases
         try:
@@ -595,14 +605,18 @@
             raise InferenceError(context=context) from e
         if not isinstance(bases, nodes.Tuple):
             # Needs to be a tuple.
-            return None
+            raise InferenceError(
+                "type.__new__() requires a tuple for bases", context=context
+            )
         try:
             inferred_bases = [next(elt.infer(context=context)) for elt in bases.elts]
         except StopIteration as e:
             raise InferenceError(context=context) from e
         if any(not isinstance(base, nodes.ClassDef) for base in inferred_bases):
             # All the bases needs to be Classes
-            return None
+            raise InferenceError(
+                "type.__new__() requires classes for bases", context=context
+            )
 
         # Verify the attributes.
         try:
@@ -611,7 +625,9 @@
             raise InferenceError(context=context) from e
         if not isinstance(attrs, nodes.Dict):
             # Needs to be a dictionary.
-            return None
+            raise InferenceError(
+                "type.__new__() requires a dict for attrs", context=context
+            )
         cls_locals: dict[str, list[InferenceResult]] = collections.defaultdict(list)
         for key, value in attrs.items:
             try:
@@ -664,9 +680,13 @@
             and self.bound.name == "type"
             and self.name == "__new__"
             and isinstance(caller, nodes.Call)
-            and len(caller.args) == 4
         ):
             # Check if we have a ``type.__new__(mcs, name, bases, attrs)`` call.
+            if len(caller.args) != 4:
+                raise InferenceError(
+                    f"type.__new__() requires 4 arguments, got {len(caller.args)}",
+                    context=context,
+                )
             new_cls = self._infer_type_new_call(caller, context)
             if new_cls:
                 return iter((new_cls,))
diff --git a/astroid/interpreter/objectmodel.py b/astroid/interpreter/objectmodel.py
index eac9e43..3745107 100644
--- a/astroid/interpreter/objectmodel.py
+++ b/astroid/interpreter/objectmodel.py
@@ -163,6 +163,33 @@
 
         return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
 
+    # Base object attributes that return Unknown as fallback placeholders.
+    @property
+    def attr___ne__(self):
+        return node_classes.Unknown(parent=self._instance)
+
+    attr___class__ = attr___ne__
+    attr___delattr__ = attr___ne__
+    attr___dir__ = attr___ne__
+    attr___doc__ = attr___ne__
+    attr___eq__ = attr___ne__
+    attr___format__ = attr___ne__
+    attr___ge__ = attr___ne__
+    attr___getattribute__ = attr___ne__
+    attr___getstate__ = attr___ne__
+    attr___gt__ = attr___ne__
+    attr___hash__ = attr___ne__
+    attr___init_subclass__ = attr___ne__
+    attr___le__ = attr___ne__
+    attr___lt__ = attr___ne__
+    attr___reduce__ = attr___ne__
+    attr___reduce_ex__ = attr___ne__
+    attr___repr__ = attr___ne__
+    attr___setattr__ = attr___ne__
+    attr___sizeof__ = attr___ne__
+    attr___str__ = attr___ne__
+    attr___subclasshook__ = attr___ne__
+
 
 class ModuleModel(ObjectModel):
     def _builtins(self):
@@ -459,30 +486,15 @@
 
         return DescriptorBoundMethod(proxy=self._instance, bound=self._instance)
 
-    # These are here just for completion.
+    # Function-specific attributes.
     @property
-    def attr___ne__(self):
+    def attr___call__(self):
         return node_classes.Unknown(parent=self._instance)
 
-    attr___subclasshook__ = attr___ne__
-    attr___str__ = attr___ne__
-    attr___sizeof__ = attr___ne__
-    attr___setattr___ = attr___ne__
-    attr___repr__ = attr___ne__
-    attr___reduce__ = attr___ne__
-    attr___reduce_ex__ = attr___ne__
-    attr___lt__ = attr___ne__
-    attr___eq__ = attr___ne__
-    attr___gt__ = attr___ne__
-    attr___format__ = attr___ne__
-    attr___delattr___ = attr___ne__
-    attr___getattribute__ = attr___ne__
-    attr___hash__ = attr___ne__
-    attr___dir__ = attr___ne__
-    attr___call__ = attr___ne__
-    attr___class__ = attr___ne__
-    attr___closure__ = attr___ne__
-    attr___code__ = attr___ne__
+    attr___builtins__ = attr___call__
+    attr___closure__ = attr___call__
+    attr___code__ = attr___call__
+    attr___type_params__ = attr___call__
 
 
 class ClassModel(ObjectModel):
diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py
index 0d3a425..1d16b74 100644
--- a/astroid/nodes/node_classes.py
+++ b/astroid/nodes/node_classes.py
@@ -1020,6 +1020,23 @@
             if elt is not None:
                 yield elt
 
+    def get_annotations(self) -> Iterator[nodes.NodeNG]:
+        """Iterate over all annotations nodes."""
+        for elt in self.posonlyargs_annotations:
+            if elt is not None:
+                yield elt
+        for elt in self.annotations:
+            if elt is not None:
+                yield elt
+        if self.varargannotation is not None:
+            yield self.varargannotation
+
+        for elt in self.kwonlyargs_annotations:
+            if elt is not None:
+                yield elt
+        if self.kwargannotation is not None:
+            yield self.kwargannotation
+
     @decorators.raise_if_nothing_inferred
     def _infer(
         self: nodes.Arguments, context: InferenceContext | None = None, **kwargs: Any
@@ -4966,9 +4983,10 @@
 
 class Unknown(_base_nodes.AssignTypeNode):
     """This node represents a node in a constructed AST where
-    introspection is not possible.  At the moment, it's only used in
-    the args attribute of FunctionDef nodes where function signature
-    introspection failed.
+    introspection is not possible.
+
+    Used in the args attribute of FunctionDef nodes where function signature
+    introspection failed, and as a placeholder in ObjectModel.
     """
 
     name = "Unknown"
diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py
index f232db3..d769b07 100644
--- a/astroid/nodes/scoped_nodes/scoped_nodes.py
+++ b/astroid/nodes/scoped_nodes/scoped_nodes.py
@@ -1620,6 +1620,16 @@
                     yield node_classes.Const(None)
                 return
 
+            # Builtin dunder methods have empty bodies, return Uninferable.
+            if (
+                len(self.body) == 0
+                and self.name.startswith("__")
+                and self.name.endswith("__")
+                and self.root().qname() == "builtins"
+            ):
+                yield util.Uninferable
+                return
+
             raise InferenceError("The function does not have any return statements")
 
         for returnnode in itertools.chain((first_return,), returns):
@@ -2348,8 +2358,10 @@
             values += classnode.locals.get(name, [])
 
         if name in self.special_attributes and class_context and not values:
-            result = [self.special_attributes.lookup(name)]
-            return result
+            special_attr = self.special_attributes.lookup(name)
+            if not isinstance(special_attr, node_classes.Unknown):
+                result = [special_attr]
+                return result
 
         if class_context:
             values += self._metaclass_lookup_attribute(name, context)
diff --git a/astroid/raw_building.py b/astroid/raw_building.py
index d1bbbd5..e7c5562 100644
--- a/astroid/raw_building.py
+++ b/astroid/raw_building.py
@@ -78,7 +78,11 @@
     """create a Const node and register it in the locals of the given
     node with the specified name
     """
-    if name not in node.special_attributes:
+    # Special case: __hash__ = None overrides ObjectModel for unhashable types.
+    # See https://docs.python.org/3/reference/datamodel.html#object.__hash__
+    if name == "__hash__" and value is None:
+        _attach_local_node(node, nodes.const_factory(value), name)
+    elif name not in node.special_attributes:
         _attach_local_node(node, nodes.const_factory(value), name)
 
 
@@ -507,7 +511,11 @@
             elif inspect.isdatadescriptor(member):
                 child = object_build_datadescriptor(node, member)
             elif isinstance(member, tuple(node_classes.CONST_CLS)):
-                if alias in node.special_attributes:
+                # Special case: __hash__ = None overrides ObjectModel for unhashable types.
+                # See https://docs.python.org/3/reference/datamodel.html#object.__hash__
+                if alias in node.special_attributes and not (
+                    alias == "__hash__" and member is None
+                ):
                     continue
                 child = nodes.const_factory(member)
             elif inspect.isroutine(member):
diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py
index 97f3a39..f7d38a9 100644
--- a/astroid/rebuilder.py
+++ b/astroid/rebuilder.py
@@ -9,6 +9,7 @@
 from __future__ import annotations
 
 import ast
+import itertools
 import sys
 import token
 from collections.abc import Callable, Collection, Generator
@@ -589,6 +590,16 @@
             type_comment_kwonlyargs=type_comment_kwonlyargs,
             type_comment_posonlyargs=type_comment_posonlyargs,
         )
+        if start_end_lineno_pairs := [
+            (arg.lineno, arg.end_lineno)
+            for arg in itertools.chain(
+                node.args, node.posonlyargs, node.kwonlyargs, [node.vararg, node.kwarg]
+            )
+            if arg
+        ]:
+            newnode.lineno = min(startend[0] for startend in start_end_lineno_pairs)
+            newnode.end_lineno = max(startend[1] for startend in start_end_lineno_pairs)
+
         # save argument names in locals:
         assert newnode.parent
         if vararg:
diff --git a/requirements_minimal.txt b/requirements_minimal.txt
index 9506151..6877e4b 100644
--- a/requirements_minimal.txt
+++ b/requirements_minimal.txt
@@ -3,7 +3,7 @@
 tbump~=6.11
 
 # Tools used to run tests
-coverage~=7.10
+coverage~=7.11
 pytest
 pytest-cov~=7.0
 mypy
diff --git a/tbump.toml b/tbump.toml
index 251afc5..42dcf88 100644
--- a/tbump.toml
+++ b/tbump.toml
@@ -1,7 +1,7 @@
 github_url = "https://github.com/pylint-dev/astroid"
 
 [version]
-current = "4.0.2"
+current = "4.1.0-dev0"
 regex = '''
 ^(?P<major>0|[1-9]\d*)
 \.
diff --git a/tests/test_inference.py b/tests/test_inference.py
index bf41e8c..dd17b60 100644
--- a/tests/test_inference.py
+++ b/tests/test_inference.py
@@ -1503,7 +1503,7 @@
 
     def test_python25_no_relative_import(self) -> None:
         ast = resources.build_file("data/package/absimport.py")
-        self.assertTrue(ast.absolute_import_activated(), True)
+        self.assertTrue(ast.absolute_import_activated())
         inferred = next(
             test_utils.get_name_node(ast, "import_package_subpackage_module").infer()
         )
@@ -1512,7 +1512,7 @@
 
     def test_nonregr_absolute_import(self) -> None:
         ast = resources.build_file("data/absimp/string.py", "data.absimp.string")
-        self.assertTrue(ast.absolute_import_activated(), True)
+        self.assertTrue(ast.absolute_import_activated())
         inferred = next(test_utils.get_name_node(ast, "string").infer())
         self.assertIsInstance(inferred, nodes.Module)
         self.assertEqual(inferred.name, "string")
@@ -5618,8 +5618,9 @@
     )
     inferred = next(node.infer())
     lenmeth = next(inferred.igetattr("__len__"))
-    with pytest.raises(InferenceError):
-        next(lenmeth.infer_call_result(None, None))
+    # Builtin dunder methods now return Uninferable instead of raising InferenceError
+    result = next(lenmeth.infer_call_result(None, None))
+    assert result is util.Uninferable
 
 
 def test_unpack_dicts_in_assignment() -> None:
diff --git a/tests/test_nodes.py b/tests/test_nodes.py
index 8d2f4d5..939c581 100644
--- a/tests/test_nodes.py
+++ b/tests/test_nodes.py
@@ -2314,6 +2314,19 @@
     assert node.args.default_value("flavor").value == "good"
 
 
+def test_arguments_annotations():
+    node = extract_node(
+        "def fruit(eat: str, /, peel: bool, *args: int, trim: float, **kwargs: bytes): ..."
+    )
+    assert isinstance(node.args, nodes.Arguments)
+    annotation_names = [
+        ann.name for ann in node.args.get_annotations() if isinstance(ann, nodes.Name)
+    ]
+    assert all(
+        name in annotation_names for name in ("str", "bool", "int", "float", "bytes")
+    )
+
+
 def test_deprecated_nodes_import_from_toplevel():
     # pylint: disable=import-outside-toplevel,no-name-in-module
     with pytest.raises(
diff --git a/tests/test_nodes_lineno.py b/tests/test_nodes_lineno.py
index f8c6f91..25fb339 100644
--- a/tests/test_nodes_lineno.py
+++ b/tests/test_nodes_lineno.py
@@ -589,6 +589,8 @@
         assert (f1.body[0].lineno, f1.body[0].col_offset) == (6, 4)
         assert (f1.body[0].end_lineno, f1.body[0].end_col_offset) == (6, 8)
 
+        assert (f1.args.lineno, f1.args.end_lineno) == (2, 4)
+
         # pos only arguments
         # TODO fix column offset: arg -> arg (AssignName)
         assert isinstance(f1.args.posonlyargs[0], nodes.AssignName)
diff --git a/tests/test_object_model.py b/tests/test_object_model.py
index f3015db..f36a0d5 100644
--- a/tests/test_object_model.py
+++ b/tests/test_object_model.py
@@ -265,10 +265,13 @@
         xml.__setattr__ #@
         xml.__reduce_ex__ #@
         xml.__lt__ #@
+        xml.__le__ #@
         xml.__eq__ #@
+        xml.__ne__ #@
+        xml.__ge__ #@
         xml.__gt__ #@
         xml.__format__ #@
-        xml.__delattr___ #@
+        xml.__delattr__ #@
         xml.__getattribute__ #@
         xml.__hash__ #@
         xml.__dir__ #@
@@ -324,9 +327,13 @@
         new_ = next(ast_nodes[10].infer())
         assert isinstance(new_, bases.BoundMethod)
 
-        # The following nodes are just here for theoretical completeness,
-        # and they either return Uninferable or raise InferenceError.
-        for ast_node in ast_nodes[11:28]:
+        # Inherited attributes return Uninferable.
+        for ast_node in ast_nodes[11:29]:
+            inferred = next(ast_node.infer())
+            self.assertIs(inferred, astroid.Uninferable)
+
+        # Attributes that don't exist on modules raise InferenceError.
+        for ast_node in ast_nodes[29:31]:
             with pytest.raises(InferenceError):
                 next(ast_node.infer())
 
@@ -449,16 +456,23 @@
 
         func.__reduce_ex__ #@
         func.__lt__ #@
+        func.__le__ #@
         func.__eq__ #@
+        func.__ne__ #@
+        func.__ge__ #@
         func.__gt__ #@
         func.__format__ #@
-        func.__delattr___ #@
+        func.__delattr__ #@
         func.__getattribute__ #@
         func.__hash__ #@
         func.__dir__ #@
         func.__class__ #@
 
         func.__setattr__ #@
+        func.__builtins__ #@
+        func.__getstate__ #@
+        func.__init_subclass__ #@
+        func.__type_params__ #@
         ''',
             module_name="fake_module",
         )
@@ -511,16 +525,11 @@
         new_ = next(ast_nodes[10].infer())
         assert isinstance(new_, bases.BoundMethod)
 
-        # The following nodes are just here for theoretical completeness,
-        # and they either return Uninferable or raise InferenceError.
-        for ast_node in ast_nodes[11:26]:
+        # Remaining attributes return Uninferable.
+        for ast_node in ast_nodes[11:34]:
             inferred = next(ast_node.infer())
             assert inferred is util.Uninferable
 
-        for ast_node in ast_nodes[26:27]:
-            with pytest.raises(InferenceError):
-                inferred = next(ast_node.infer())
-
     def test_empty_return_annotation(self) -> None:
         ast_node = builder.extract_node(
             """
@@ -897,3 +906,112 @@
     assert next(apple.infer()) is astroid.Uninferable
     assert isinstance(pear, nodes.Attribute)
     assert next(pear.infer()) is astroid.Uninferable
+
+
+def test_object_dunder_methods_can_be_overridden() -> None:
+    """Test that ObjectModel dunders don't block class overrides."""
+    # Test instance method override
+    eq_result = builder.extract_node(
+        """
+        class MyClass:
+            def __eq__(self, other):
+                return "custom equality"
+
+        MyClass().__eq__(None)  #@
+        """
+    )
+    inferred = next(eq_result.infer())
+    assert isinstance(inferred, nodes.Const)
+    assert inferred.value == "custom equality"
+
+    # Test that __eq__ on instance returns a bound method
+    eq_method = builder.extract_node(
+        """
+        class MyClass:
+            def __eq__(self, other):
+                return True
+
+        MyClass().__eq__  #@
+        """
+    )
+    inferred = next(eq_method.infer())
+    assert isinstance(inferred, astroid.BoundMethod)
+
+    # Test other commonly overridden dunders
+    for dunder, return_val in (
+        ("__ne__", "not equal"),
+        ("__lt__", "less than"),
+        ("__le__", "less or equal"),
+        ("__gt__", "greater than"),
+        ("__ge__", "greater or equal"),
+        ("__str__", "string repr"),
+        ("__repr__", "repr"),
+        ("__hash__", 42),
+    ):
+        node = builder.extract_node(
+            f"""
+            class MyClass:
+                def {dunder}(self, *args):
+                    return {return_val!r}
+
+            MyClass().{dunder}()  #@
+            """
+        )
+        inferred = next(node.infer())
+        assert isinstance(inferred, nodes.Const), f"{dunder} failed to infer correctly"
+        assert inferred.value == return_val, f"{dunder} returned wrong value"
+
+
+def test_unoverridden_object_dunders_return_uninferable() -> None:
+    """Test that un-overridden object dunders return Uninferable when called."""
+    for dunder in (
+        "__eq__",
+        "__hash__",
+        "__lt__",
+        "__le__",
+        "__gt__",
+        "__ge__",
+        "__ne__",
+    ):
+        node = builder.extract_node(
+            f"""
+            class MyClass:
+                pass
+
+            MyClass().{dunder}(None) if "{dunder}" != "__hash__" else MyClass().{dunder}()  #@
+            """
+        )
+        result = next(node.infer())
+        assert result is util.Uninferable
+
+
+def test_all_object_dunders_accessible() -> None:
+    """Test that object dunders are accessible on classes and instances."""
+    # Use actual dunders from object in the current Python version
+    object_dunders = [attr for attr in dir(object) if attr.startswith("__")]
+
+    cls, instance = builder.extract_node(
+        """
+        class MyClass:
+            pass
+
+        MyClass  #@
+        MyClass()  #@
+        """
+    )
+    cls = next(cls.infer())
+    instance = next(instance.infer())
+
+    for dunder in object_dunders:
+        assert cls.getattr(dunder)
+        assert instance.getattr(dunder)
+
+
+def test_hash_none_for_unhashable_builtins() -> None:
+    """Test that unhashable builtin types have __hash__ = None."""
+    for type_name in ("list", "dict", "set"):
+        node = builder.extract_node(f"{type_name}  #@")
+        cls = next(node.infer())
+        hash_attr = cls.getattr("__hash__")[0]
+        assert isinstance(hash_attr, nodes.Const)
+        assert hash_attr.value is None