@@ -910,7 +910,7 @@ def draw_boolean(
910
910
pass
911
911
912
912
913
- @attr .s (slots = True , repr = False )
913
+ @attr .s (slots = True , repr = False , eq = False )
914
914
class IRNode :
915
915
ir_type : IRTypeName = attr .ib ()
916
916
value : IRType = attr .ib ()
@@ -928,6 +928,17 @@ def copy(self, *, with_value: IRType) -> "IRNode":
928
928
was_forced = self .was_forced ,
929
929
)
930
930
931
+ def __eq__ (self , other ):
932
+ if not isinstance (other , IRNode ):
933
+ return NotImplemented
934
+
935
+ return (
936
+ self .ir_type == other .ir_type
937
+ and ir_value_equal (self .ir_type , self .value , other .value )
938
+ and ir_kwargs_equal (self .ir_type , self .kwargs , other .kwargs )
939
+ and self .was_forced == other .was_forced
940
+ )
941
+
931
942
def __repr__ (self ):
932
943
# repr to avoid "BytesWarning: str() on a bytes instance" for bytes nodes
933
944
forced_marker = " [forced]" if self .was_forced else ""
@@ -967,6 +978,24 @@ def ir_value_permitted(value, ir_type, kwargs):
967
978
raise NotImplementedError (f"unhandled type { type (value )} of ir value { value } " )
968
979
969
980
981
+ def ir_value_equal (ir_type , v1 , v2 ):
982
+ if ir_type != "float" :
983
+ return v1 == v2
984
+ return float_to_int (v1 ) == float_to_int (v2 )
985
+
986
+
987
+ def ir_kwargs_equal (ir_type , kwargs1 , kwargs2 ):
988
+ if ir_type != "float" :
989
+ return kwargs1 == kwargs2
990
+ return (
991
+ float_to_int (kwargs1 ["min_value" ]) == float_to_int (kwargs2 ["min_value" ])
992
+ and float_to_int (kwargs1 ["max_value" ]) == float_to_int (kwargs2 ["max_value" ])
993
+ and kwargs1 ["allow_nan" ] == kwargs2 ["allow_nan" ]
994
+ and kwargs1 ["smallest_nonzero_magnitude" ]
995
+ == kwargs2 ["smallest_nonzero_magnitude" ]
996
+ )
997
+
998
+
970
999
@dataclass_transform ()
971
1000
@attr .s (slots = True )
972
1001
class ConjectureResult :
@@ -1880,7 +1909,7 @@ def draw_integer(
1880
1909
)
1881
1910
1882
1911
if self .ir_tree_nodes is not None and observe :
1883
- node = self ._pop_ir_tree_node ("integer" , kwargs )
1912
+ node = self ._pop_ir_tree_node ("integer" , kwargs , forced = forced )
1884
1913
assert isinstance (node .value , int )
1885
1914
forced = node .value
1886
1915
fake_forced = not node .was_forced
@@ -1936,7 +1965,7 @@ def draw_float(
1936
1965
)
1937
1966
1938
1967
if self .ir_tree_nodes is not None and observe :
1939
- node = self ._pop_ir_tree_node ("float" , kwargs )
1968
+ node = self ._pop_ir_tree_node ("float" , kwargs , forced = forced )
1940
1969
assert isinstance (node .value , float )
1941
1970
forced = node .value
1942
1971
fake_forced = not node .was_forced
@@ -1977,7 +2006,7 @@ def draw_string(
1977
2006
},
1978
2007
)
1979
2008
if self .ir_tree_nodes is not None and observe :
1980
- node = self ._pop_ir_tree_node ("string" , kwargs )
2009
+ node = self ._pop_ir_tree_node ("string" , kwargs , forced = forced )
1981
2010
assert isinstance (node .value , str )
1982
2011
forced = node .value
1983
2012
fake_forced = not node .was_forced
@@ -2012,7 +2041,7 @@ def draw_bytes(
2012
2041
kwargs : BytesKWargs = self ._pooled_kwargs ("bytes" , {"size" : size })
2013
2042
2014
2043
if self .ir_tree_nodes is not None and observe :
2015
- node = self ._pop_ir_tree_node ("bytes" , kwargs )
2044
+ node = self ._pop_ir_tree_node ("bytes" , kwargs , forced = forced )
2016
2045
assert isinstance (node .value , bytes )
2017
2046
forced = node .value
2018
2047
fake_forced = not node .was_forced
@@ -2053,7 +2082,7 @@ def draw_boolean(
2053
2082
kwargs : BooleanKWargs = self ._pooled_kwargs ("boolean" , {"p" : p })
2054
2083
2055
2084
if self .ir_tree_nodes is not None and observe :
2056
- node = self ._pop_ir_tree_node ("boolean" , kwargs )
2085
+ node = self ._pop_ir_tree_node ("boolean" , kwargs , forced = forced )
2057
2086
assert isinstance (node .value , bool )
2058
2087
forced = node .value
2059
2088
fake_forced = not node .was_forced
@@ -2093,7 +2122,9 @@ def _pooled_kwargs(self, ir_type, kwargs):
2093
2122
POOLED_KWARGS_CACHE [key ] = kwargs
2094
2123
return kwargs
2095
2124
2096
- def _pop_ir_tree_node (self , ir_type : IRTypeName , kwargs : IRKWargsType ) -> IRNode :
2125
+ def _pop_ir_tree_node (
2126
+ self , ir_type : IRTypeName , kwargs : IRKWargsType , * , forced : Optional [IRType ]
2127
+ ) -> IRNode :
2097
2128
assert self .ir_tree_nodes is not None
2098
2129
2099
2130
if self .ir_tree_nodes == []:
@@ -2120,6 +2151,32 @@ def _pop_ir_tree_node(self, ir_type: IRTypeName, kwargs: IRKWargsType) -> IRNode
2120
2151
if not ir_value_permitted (node .value , node .ir_type , kwargs ):
2121
2152
self .mark_invalid () # pragma: no cover # FIXME @tybug
2122
2153
2154
+ if forced is not None :
2155
+ # if we expected a forced node but are instead returning a non-forced
2156
+ # node, something has gone terribly wrong. If we allowed this combination,
2157
+ # we risk violating core invariants that rely on forced draws being,
2158
+ # well, forced to a particular value.
2159
+ #
2160
+ # In particular, this can manifest while shrinking. Consider the tree
2161
+ # [boolean True [forced] {"p": 0.5}]
2162
+ # [boolean False {"p": 0.5}]
2163
+ #
2164
+ # and the shrinker tries to reorder these to
2165
+ # [boolean False {"p": 0.5}]
2166
+ # [boolean True [forced] {"p": 0.5}].
2167
+ #
2168
+ # However, maybe we got lucky and the non-forced node is returning
2169
+ # the same value that was expected from the forced draw. We lucked
2170
+ # into an aligned tree in this case and can let it slide.
2171
+ if not node .was_forced and not ir_value_equal (ir_type , forced , node .value ):
2172
+ self .mark_invalid ()
2173
+
2174
+ # similarly, if we expected a forced node with a certain value, and
2175
+ # are returning a forced node with a different value, this is an
2176
+ # equally bad misalignment.
2177
+ if node .was_forced and not ir_value_equal (ir_type , forced , node .value ):
2178
+ self .mark_invalid ()
2179
+
2123
2180
return node
2124
2181
2125
2182
def as_result (self ) -> Union [ConjectureResult , _Overrun ]:
0 commit comments