@@ -3268,9 +3268,11 @@ def testfunc(args):
32683268 self .assertIn ("_BINARY_OP_TRUEDIV_FLOAT_INPLACE_RIGHT" , uops )
32693269
32703270 def test_float_truediv_type_propagation (self ):
3271- # (a/b) + (c/d): inner divisions are generic _BINARY_OP but
3272- # type propagation marks their results as float, so the +
3273- # is specialized and the += uses inplace on the unique result
3271+ # (a/b) + (c/d): inner divisions are generic _BINARY_OP.
3272+ # Type propagation only marks results as float when operand
3273+ # types are known int/float (to avoid mistyping Fraction etc.).
3274+ # With unknown-type locals, the + is specialized via tier 1
3275+ # guards, not via optimizer type propagation.
32743276 def testfunc (args ):
32753277 a , b , c , d , n = args
32763278 total = 0.0
@@ -3283,29 +3285,42 @@ def testfunc(args):
32833285 self .assertAlmostEqual (res , expected )
32843286 self .assertIsNotNone (ex )
32853287 uops = get_opnames (ex )
3286- # The + between the two division results should use inplace
3287- # (the a/b result is unique from type propagation)
3288- self .assertIn ("_BINARY_OP_ADD_FLOAT_INPLACE" , uops )
3289- # The += should also use inplace (the + result is unique)
3288+ # The + between the two division results is specialized
3289+ self .assertIn ("_BINARY_OP_ADD_FLOAT" , uops )
3290+ # The += uses inplace (the + result is unique)
32903291 self .assertIn ("_BINARY_OP_ADD_FLOAT_INPLACE_RIGHT" , uops )
32913292
3292- def test_float_truediv_unique_result_enables_inplace_add (self ):
3293- # a / b: the generic division result is marked as unique float
3294- # by type propagation, so total += (a / b) uses inplace add
3293+ def test_float_truediv_non_float_type_no_crash (self ):
3294+ # Fraction / Fraction goes through _BINARY_OP with NB_TRUE_DIVIDE
3295+ # but returns Fraction, not float. The optimizer must not assume
3296+ # the result is float for non-int/float operands. See gh-146306.
3297+ from fractions import Fraction
32953298 def testfunc (args ):
32963299 a , b , n = args
3297- total = 0.0
3300+ total = Fraction ( 0 )
32983301 for _ in range (n ):
32993302 total += a / b
3300- return total
3303+ return float ( total )
33013304
3302- res , ex = self ._run_with_optimizer (testfunc , (10.0 , 3.0 , TIER2_THRESHOLD ))
3303- expected = TIER2_THRESHOLD * (10.0 / 3.0 )
3305+ res , ex = self ._run_with_optimizer (testfunc , (Fraction ( 10 ), Fraction ( 3 ) , TIER2_THRESHOLD ))
3306+ expected = float ( TIER2_THRESHOLD * Fraction (10 , 3 ) )
33043307 self .assertAlmostEqual (res , expected )
3305- self .assertIsNotNone (ex )
3306- uops = get_opnames (ex )
3307- # The += uses inplace because the division result is unique
3308- self .assertIn ("_BINARY_OP_ADD_FLOAT_INPLACE_RIGHT" , uops )
3308+
3309+ def test_float_truediv_mixed_float_fraction_no_crash (self ):
3310+ # float / Fraction: lhs is known float from a prior guard,
3311+ # but rhs is Fraction. The guard insertion for rhs should
3312+ # deopt cleanly at runtime, not crash.
3313+ from fractions import Fraction
3314+ def testfunc (args ):
3315+ a , b , c , n = args
3316+ total = 0.0
3317+ for _ in range (n ):
3318+ total += (a + b ) / c # (a+b) is float, c is Fraction
3319+ return total
3320+
3321+ res , ex = self ._run_with_optimizer (testfunc , (2.0 , 3.0 , Fraction (4 ), TIER2_THRESHOLD ))
3322+ expected = TIER2_THRESHOLD * (5.0 / Fraction (4 ))
3323+ self .assertAlmostEqual (res , float (expected ))
33093324
33103325 def test_load_attr_instance_value (self ):
33113326 def testfunc (n ):
0 commit comments