@@ -265,10 +265,13 @@ def test_module_model(self) -> None:
265265 xml.__setattr__ #@
266266 xml.__reduce_ex__ #@
267267 xml.__lt__ #@
268+ xml.__le__ #@
268269 xml.__eq__ #@
270+ xml.__ne__ #@
271+ xml.__ge__ #@
269272 xml.__gt__ #@
270273 xml.__format__ #@
271- xml.__delattr___ #@
274+ xml.__delattr__ #@
272275 xml.__getattribute__ #@
273276 xml.__hash__ #@
274277 xml.__dir__ #@
@@ -324,9 +327,13 @@ def test_module_model(self) -> None:
324327 new_ = next (ast_nodes [10 ].infer ())
325328 assert isinstance (new_ , bases .BoundMethod )
326329
327- # The following nodes are just here for theoretical completeness,
328- # and they either return Uninferable or raise InferenceError.
329- for ast_node in ast_nodes [11 :28 ]:
330+ # Inherited attributes return Uninferable.
331+ for ast_node in ast_nodes [11 :29 ]:
332+ inferred = next (ast_node .infer ())
333+ self .assertIs (inferred , astroid .Uninferable )
334+
335+ # Attributes that don't exist on modules raise InferenceError.
336+ for ast_node in ast_nodes [29 :31 ]:
330337 with pytest .raises (InferenceError ):
331338 next (ast_node .infer ())
332339
@@ -449,16 +456,23 @@ def func(a=1, b=2):
449456
450457 func.__reduce_ex__ #@
451458 func.__lt__ #@
459+ func.__le__ #@
452460 func.__eq__ #@
461+ func.__ne__ #@
462+ func.__ge__ #@
453463 func.__gt__ #@
454464 func.__format__ #@
455- func.__delattr___ #@
465+ func.__delattr__ #@
456466 func.__getattribute__ #@
457467 func.__hash__ #@
458468 func.__dir__ #@
459469 func.__class__ #@
460470
461471 func.__setattr__ #@
472+ func.__builtins__ #@
473+ func.__getstate__ #@
474+ func.__init_subclass__ #@
475+ func.__type_params__ #@
462476 ''' ,
463477 module_name = "fake_module" ,
464478 )
@@ -511,16 +525,11 @@ def func(a=1, b=2):
511525 new_ = next (ast_nodes [10 ].infer ())
512526 assert isinstance (new_ , bases .BoundMethod )
513527
514- # The following nodes are just here for theoretical completeness,
515- # and they either return Uninferable or raise InferenceError.
516- for ast_node in ast_nodes [11 :26 ]:
528+ # Remaining attributes return Uninferable.
529+ for ast_node in ast_nodes [11 :34 ]:
517530 inferred = next (ast_node .infer ())
518531 assert inferred is util .Uninferable
519532
520- for ast_node in ast_nodes [26 :27 ]:
521- with pytest .raises (InferenceError ):
522- inferred = next (ast_node .infer ())
523-
524533 def test_empty_return_annotation (self ) -> None :
525534 ast_node = builder .extract_node (
526535 """
@@ -897,3 +906,112 @@ class Apple(TypedDict):
897906 assert next (apple .infer ()) is astroid .Uninferable
898907 assert isinstance (pear , nodes .Attribute )
899908 assert next (pear .infer ()) is astroid .Uninferable
909+
910+
911+ def test_object_dunder_methods_can_be_overridden () -> None :
912+ """Test that ObjectModel dunders don't block class overrides."""
913+ # Test instance method override
914+ eq_result = builder .extract_node (
915+ """
916+ class MyClass:
917+ def __eq__(self, other):
918+ return "custom equality"
919+
920+ MyClass().__eq__(None) #@
921+ """
922+ )
923+ inferred = next (eq_result .infer ())
924+ assert isinstance (inferred , nodes .Const )
925+ assert inferred .value == "custom equality"
926+
927+ # Test that __eq__ on instance returns a bound method
928+ eq_method = builder .extract_node (
929+ """
930+ class MyClass:
931+ def __eq__(self, other):
932+ return True
933+
934+ MyClass().__eq__ #@
935+ """
936+ )
937+ inferred = next (eq_method .infer ())
938+ assert isinstance (inferred , astroid .BoundMethod )
939+
940+ # Test other commonly overridden dunders
941+ for dunder , return_val in (
942+ ("__ne__" , "not equal" ),
943+ ("__lt__" , "less than" ),
944+ ("__le__" , "less or equal" ),
945+ ("__gt__" , "greater than" ),
946+ ("__ge__" , "greater or equal" ),
947+ ("__str__" , "string repr" ),
948+ ("__repr__" , "repr" ),
949+ ("__hash__" , 42 ),
950+ ):
951+ node = builder .extract_node (
952+ f"""
953+ class MyClass:
954+ def { dunder } (self, *args):
955+ return { return_val !r}
956+
957+ MyClass().{ dunder } () #@
958+ """
959+ )
960+ inferred = next (node .infer ())
961+ assert isinstance (inferred , nodes .Const ), f"{ dunder } failed to infer correctly"
962+ assert inferred .value == return_val , f"{ dunder } returned wrong value"
963+
964+
965+ def test_unoverridden_object_dunders_return_uninferable () -> None :
966+ """Test that un-overridden object dunders return Uninferable when called."""
967+ for dunder in (
968+ "__eq__" ,
969+ "__hash__" ,
970+ "__lt__" ,
971+ "__le__" ,
972+ "__gt__" ,
973+ "__ge__" ,
974+ "__ne__" ,
975+ ):
976+ node = builder .extract_node (
977+ f"""
978+ class MyClass:
979+ pass
980+
981+ MyClass().{ dunder } (None) if "{ dunder } " != "__hash__" else MyClass().{ dunder } () #@
982+ """
983+ )
984+ result = next (node .infer ())
985+ assert result is util .Uninferable
986+
987+
988+ def test_all_object_dunders_accessible () -> None :
989+ """Test that object dunders are accessible on classes and instances."""
990+ # Use actual dunders from object in the current Python version
991+ object_dunders = [attr for attr in dir (object ) if attr .startswith ("__" )]
992+
993+ cls , instance = builder .extract_node (
994+ """
995+ class MyClass:
996+ pass
997+
998+ MyClass #@
999+ MyClass() #@
1000+ """
1001+ )
1002+ cls = next (cls .infer ())
1003+ instance = next (instance .infer ())
1004+
1005+ for dunder in object_dunders :
1006+ assert cls .getattr (dunder )
1007+ assert instance .getattr (dunder )
1008+
1009+
1010+ def test_hash_none_for_unhashable_builtins () -> None :
1011+ """Test that unhashable builtin types have __hash__ = None."""
1012+ for type_name in ("list" , "dict" , "set" ):
1013+ node = builder .extract_node (f"{ type_name } #@" )
1014+ cls = next (node .infer ())
1015+ hash_attr = cls .getattr ("__hash__" )[0 ]
1016+ assert isinstance (hash_attr , nodes .Const )
1017+ assert hash_attr .value is None
0 commit comments