@@ -206,6 +206,10 @@ def test_bitwise_operators(uint_class: Type[BaseUint]) -> None:
206206
207207 with pytest .raises (TypeError ):
208208 _ = a & 1
209+ with pytest .raises (TypeError ):
210+ _ = a | 1
211+ with pytest .raises (TypeError ):
212+ _ = a ^ 1
209213
210214
211215@pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
@@ -420,3 +424,253 @@ def test_deserialize_stream_too_short(self, uint_class: Type[BaseUint]) -> None:
420424 stream = io .BytesIO (b"\x00 " * (byte_length - 1 ))
421425 with pytest .raises (SSZSerializationError , match = "expected .* bytes, got" ):
422426 uint_class .deserialize (stream , scope = byte_length )
427+
428+
429+ class TestForwardArithmeticTypeErrors :
430+ """Tests that forward arithmetic operators reject plain int operands.
431+
432+ When calling e.g. Uint64(5).__add__(3), the forward operator must raise
433+ TypeError because 3 is a plain int, not a BaseUint subclass.
434+ """
435+
436+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
437+ @pytest .mark .parametrize (
438+ "method, op_symbol" ,
439+ [
440+ ("__add__" , r"\+" ),
441+ ("__sub__" , r"-" ),
442+ ("__mul__" , r"\*" ),
443+ ("__floordiv__" , r"//" ),
444+ ("__mod__" , r"%" ),
445+ ],
446+ )
447+ def test_forward_operator_rejects_plain_int (
448+ self , uint_class : Type [BaseUint ], method : str , op_symbol : str
449+ ) -> None :
450+ """Forward arithmetic operator raises TypeError when given a plain int."""
451+ # Call the dunder method directly with a plain int operand.
452+ with pytest .raises (TypeError , match = op_symbol ):
453+ getattr (uint_class (5 ), method )(3 )
454+
455+
456+ class TestReverseArithmeticSuccessPaths :
457+ """Tests that reverse arithmetic operators succeed when both operands are BaseUint.
458+
459+ Calling the reverse dunder directly (e.g. Uint64(3).__radd__(Uint64(5)))
460+ exercises the success return path of each reverse operator.
461+ """
462+
463+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
464+ def test_radd_success (self , uint_class : Type [BaseUint ]) -> None :
465+ """Reverse add returns the correct sum when called directly."""
466+ # __radd__(other) computes other + self
467+ result = uint_class (3 ).__radd__ (uint_class (5 ))
468+ assert result == uint_class (8 )
469+ assert isinstance (result , uint_class )
470+
471+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
472+ def test_rsub_success (self , uint_class : Type [BaseUint ]) -> None :
473+ """Reverse sub returns the correct difference when called directly."""
474+ # __rsub__(other) computes other - self
475+ result = uint_class (3 ).__rsub__ (uint_class (10 ))
476+ assert result == uint_class (7 )
477+ assert isinstance (result , uint_class )
478+
479+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
480+ def test_rmul_success (self , uint_class : Type [BaseUint ]) -> None :
481+ """Reverse mul returns the correct product when called directly."""
482+ # __rmul__(other) computes other * self
483+ result = uint_class (3 ).__rmul__ (uint_class (5 ))
484+ assert result == uint_class (15 )
485+ assert isinstance (result , uint_class )
486+
487+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
488+ def test_rfloordiv_success (self , uint_class : Type [BaseUint ]) -> None :
489+ """Reverse floordiv returns the correct quotient when called directly."""
490+ # __rfloordiv__(other) computes other // self
491+ result = uint_class (3 ).__rfloordiv__ (uint_class (10 ))
492+ assert result == uint_class (3 )
493+ assert isinstance (result , uint_class )
494+
495+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
496+ def test_rmod_success (self , uint_class : Type [BaseUint ]) -> None :
497+ """Reverse mod returns the correct remainder when called directly."""
498+ # __rmod__(other) computes other % self
499+ result = uint_class (3 ).__rmod__ (uint_class (10 ))
500+ assert result == uint_class (1 )
501+ assert isinstance (result , uint_class )
502+
503+
504+ class TestPowAndRpow :
505+ """Tests for exponentiation operators including modulo and reverse paths."""
506+
507+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
508+ def test_pow_with_modulo (self , uint_class : Type [BaseUint ]) -> None :
509+ """Three-argument pow(base, exp, mod) validates the modulo and returns correct result."""
510+ # pow(2, 10, 100) == 1024 % 100 == 24
511+ result = pow (uint_class (2 ), 10 , 100 )
512+ assert result == uint_class (24 )
513+ assert isinstance (result , uint_class )
514+
515+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
516+ def test_pow_with_bool_modulo_raises (self , uint_class : Type [BaseUint ]) -> None :
517+ """Three-argument pow rejects a bool as the modulo operand."""
518+ with pytest .raises (TypeError , match = r"expected 'int' but got 'bool'" ):
519+ pow (uint_class (2 ), 10 , True )
520+
521+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
522+ def test_rpow_success (self , uint_class : Type [BaseUint ]) -> None :
523+ """Reverse pow computes base ** self when called directly."""
524+ # __rpow__(base) computes base ** self => 2 ** 3 == 8
525+ result = uint_class (3 ).__rpow__ (uint_class (2 ))
526+ assert result == uint_class (8 )
527+ assert isinstance (result , uint_class )
528+
529+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
530+ def test_rpow_rejects_bool (self , uint_class : Type [BaseUint ]) -> None :
531+ """Reverse pow rejects a bool as the base operand."""
532+ with pytest .raises (TypeError , match = r"expected 'int' but got 'bool'" ):
533+ uint_class (3 ).__rpow__ (True )
534+
535+
536+ class TestValidateIntOperand :
537+ """Tests for _validate_int_operand which rejects bools and non-ints."""
538+
539+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
540+ def test_pow_rejects_bool_exponent (self , uint_class : Type [BaseUint ]) -> None :
541+ """Exponentiation rejects a bool as the exponent."""
542+ with pytest .raises (TypeError , match = r"expected 'int' but got 'bool'" ):
543+ uint_class (2 ) ** True
544+
545+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
546+ def test_pow_rejects_string_exponent (self , uint_class : Type [BaseUint ]) -> None :
547+ """Exponentiation rejects a string as the exponent."""
548+ with pytest .raises (TypeError , match = r"expected 'int' but got 'str'" ):
549+ uint_class (2 ) ** "3" # type: ignore[operator]
550+
551+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
552+ def test_lshift_rejects_bool (self , uint_class : Type [BaseUint ]) -> None :
553+ """Left shift rejects a bool as the shift amount."""
554+ with pytest .raises (TypeError , match = r"expected 'int' but got 'bool'" ):
555+ uint_class (1 ) << True
556+
557+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
558+ def test_rshift_rejects_bool (self , uint_class : Type [BaseUint ]) -> None :
559+ """Right shift rejects a bool as the shift amount."""
560+ with pytest .raises (TypeError , match = r"expected 'int' but got 'bool'" ):
561+ uint_class (8 ) >> True
562+
563+
564+ class TestDivmodEdgeCases :
565+ """Tests for divmod type error and reverse divmod paths."""
566+
567+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
568+ def test_divmod_rejects_plain_int (self , uint_class : Type [BaseUint ]) -> None :
569+ """Forward divmod raises TypeError when the divisor is a plain int."""
570+ with pytest .raises (TypeError , match = "divmod" ):
571+ divmod (uint_class (10 ), 3 ) # type: ignore[call-overload]
572+
573+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
574+ def test_rdivmod_success (self , uint_class : Type [BaseUint ]) -> None :
575+ """Reverse divmod returns correct (quotient, remainder) when called directly."""
576+ # __rdivmod__(other) computes divmod(other, self) => divmod(10, 3) == (3, 1)
577+ q , r = uint_class (3 ).__rdivmod__ (uint_class (10 ))
578+ assert q == uint_class (3 )
579+ assert r == uint_class (1 )
580+ assert isinstance (q , uint_class )
581+ assert isinstance (r , uint_class )
582+
583+
584+ class TestReverseBitwiseOperators :
585+ """Tests for reverse bitwise operator delegation paths."""
586+
587+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
588+ def test_rand_delegates_to_and (self , uint_class : Type [BaseUint ]) -> None :
589+ """Reverse AND delegates to forward AND and returns the correct result."""
590+ # __rand__ delegates to __and__
591+ result = uint_class (0b1100 ).__rand__ (uint_class (0b1010 ))
592+ assert result == uint_class (0b1000 )
593+ assert isinstance (result , uint_class )
594+
595+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
596+ def test_ror_delegates_to_or (self , uint_class : Type [BaseUint ]) -> None :
597+ """Reverse OR delegates to forward OR and returns the correct result."""
598+ # __ror__ delegates to __or__
599+ result = uint_class (0b1100 ).__ror__ (uint_class (0b1010 ))
600+ assert result == uint_class (0b1110 )
601+ assert isinstance (result , uint_class )
602+
603+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
604+ def test_rxor_delegates_to_xor (self , uint_class : Type [BaseUint ]) -> None :
605+ """Reverse XOR delegates to forward XOR and returns the correct result."""
606+ # __rxor__ delegates to __xor__
607+ result = uint_class (0b1100 ).__rxor__ (uint_class (0b1010 ))
608+ assert result == uint_class (0b0110 )
609+ assert isinstance (result , uint_class )
610+
611+
612+ class TestReverseShiftOperators :
613+ """Tests for reverse left-shift and right-shift operator paths."""
614+
615+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
616+ def test_rlshift_success (self , uint_class : Type [BaseUint ]) -> None :
617+ """Reverse left shift computes other << self."""
618+ # __rlshift__(other) computes other << self => 1 << 2 == 4
619+ result = uint_class (2 ).__rlshift__ (1 )
620+ assert result == uint_class (4 )
621+ assert isinstance (result , uint_class )
622+
623+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
624+ def test_rlshift_rejects_bool (self , uint_class : Type [BaseUint ]) -> None :
625+ """Reverse left shift rejects a bool operand."""
626+ with pytest .raises (TypeError , match = r"expected 'int' but got 'bool'" ):
627+ uint_class (2 ).__rlshift__ (True )
628+
629+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
630+ def test_rrshift_success (self , uint_class : Type [BaseUint ]) -> None :
631+ """Reverse right shift computes other >> self."""
632+ # __rrshift__(other) computes other >> self => 8 >> 2 == 2
633+ result = uint_class (2 ).__rrshift__ (8 )
634+ assert result == uint_class (2 )
635+ assert isinstance (result , uint_class )
636+
637+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
638+ def test_rrshift_rejects_bool (self , uint_class : Type [BaseUint ]) -> None :
639+ """Reverse right shift rejects a bool operand."""
640+ with pytest .raises (TypeError , match = r"expected 'int' but got 'bool'" ):
641+ uint_class (2 ).__rrshift__ (True )
642+
643+
644+ class TestComparisonTypeErrors :
645+ """Tests that comparison operators raise TypeError when given plain int operands.
646+
647+ The existing test_all_comparisons_with_other_types_raise_error uses the operator
648+ syntax (e.g., `uint < 10`) which for __lt__ and __le__ may be resolved by Python
649+ as int.__gt__ and int.__ge__ instead. Calling the dunder directly ensures the
650+ BaseUint implementation is exercised.
651+ """
652+
653+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
654+ def test_lt_rejects_plain_int (self , uint_class : Type [BaseUint ]) -> None :
655+ """Less-than raises TypeError when compared to a plain int directly."""
656+ with pytest .raises (TypeError , match = "<" ):
657+ uint_class (5 ).__lt__ (10 )
658+
659+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
660+ def test_le_rejects_plain_int (self , uint_class : Type [BaseUint ]) -> None :
661+ """Less-than-or-equal raises TypeError when compared to a plain int directly."""
662+ with pytest .raises (TypeError , match = "<=" ):
663+ uint_class (5 ).__le__ (10 )
664+
665+
666+ class TestIndexReturnsPlainInt :
667+ """Tests that __index__ returns a plain int, not a BaseUint subclass."""
668+
669+ @pytest .mark .parametrize ("uint_class" , ALL_UINT_TYPES )
670+ def test_index_returns_plain_int (self , uint_class : Type [BaseUint ]) -> None :
671+ """__index__ returns a plain int so that built-in operations receive a raw integer."""
672+ result = uint_class (42 ).__index__ ()
673+ # The value must be correct.
674+ assert result == 42
675+ # The type must be plain int, not a BaseUint subclass.
676+ assert type (result ) is int
0 commit comments