Repository: HypothesisWorks/hypothesis

Issue:
integers strategy can fail with assertion error: `assert min_value is not None`
### Versions tested
* hypothesis 6.100.4
* Python 3.12.3
* pytest 8.2.0

### Summary
`@given(i=integers())` exploded with assertion error on `assert min_value is not None`. Huh?

### Reproducer
Trivial do-nothing test case:

```python
from hypothesis import given, settings
from hypothesis.strategies import integers


@given(i=integers())
@settings(max_examples=50000)
def test_dummy(i):
    assert i is i
```

Run it with: `pytest --hypothesis-seed=55662285600128535524272749666366957831 tgiven.py`

On my machine it takes about 12 seconds until it asserts, but it asserts reliably.

### Traceback
```
    @given(i=integers())
>   @settings(max_examples=50000)

tgiven.py:6: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.12/site-packages/hypothesis/internal/conjecture/engine.py:543: in run
    self._run()
/usr/lib/python3.12/site-packages/hypothesis/internal/conjecture/engine.py:973: in _run
    self.generate_new_examples()
/usr/lib/python3.12/site-packages/hypothesis/internal/conjecture/engine.py:755: in generate_new_examples
    prefix = self.generate_novel_prefix()
/usr/lib/python3.12/site-packages/hypothesis/internal/conjecture/engine.py:399: in generate_novel_prefix
    return self.tree.generate_novel_prefix(self.random)
/usr/lib/python3.12/site-packages/hypothesis/internal/conjecture/datatree.py:766: in generate_novel_prefix
    (v, buf) = self._draw_from_cache(
/usr/lib/python3.12/site-packages/hypothesis/internal/conjecture/datatree.py:904: in _draw_from_cache
    for v in generator:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

ir_type = 'integer', kwargs = {'max_value': None, 'min_value': None, 'shrink_towards': 0, 'weights': None}

    def all_children(ir_type, kwargs):
        if ir_type == "integer":
            min_value = kwargs["min_value"]
            max_value = kwargs["max_value"]
            weights = kwargs["weights"]
            # it's a bit annoying (but completely feasible) to implement the cases
            # other than "both sides bounded" here. We haven't needed to yet because
            # in practice we don't struggle with unbounded integer generation.
>           assert min_value is not None
E           assert None is not None

/usr/lib/python3.12/site-packages/hypothesis/internal/conjecture/datatree.py:270: AssertionError
============================================= short test summary info ==============================================
FAILED tgiven.py::test_dummy - assert None is not None
```

Debug log generated with `pytest --hypothesis-verbosity=debug --full-trace --hypothesis-seed=55662285600128535524272749666366957831 tgiven.py &> debug.log` is attached here: 

[debug.log.gz](https://github.com/HypothesisWorks/hypothesis/files/15219681/debug.log.gz)




Discussion:

Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/datatree.py b/hypothesis-python/src/hypothesis/internal/conjecture/datatree.py
--- a/hypothesis-python/src/hypothesis/internal/conjecture/datatree.py
+++ b/hypothesis-python/src/hypothesis/internal/conjecture/datatree.py
@@ -265,20 +265,37 @@ def all_children(ir_type, kwargs):
         min_value = kwargs["min_value"]
         max_value = kwargs["max_value"]
         weights = kwargs["weights"]
-        # it's a bit annoying (but completely feasible) to implement the cases
-        # other than "both sides bounded" here. We haven't needed to yet because
-        # in practice we don't struggle with unbounded integer generation.
-        assert min_value is not None
-        assert max_value is not None
-
-        if weights is None:
-            yield from range(min_value, max_value + 1)
+
+        if min_value is None and max_value is None:
+            # full 128 bit range.
+            yield from range(-(2**127) + 1, 2**127 - 1)
+
+        elif min_value is not None and max_value is not None:
+            if weights is None:
+                yield from range(min_value, max_value + 1)
+            else:
+                # skip any values with a corresponding weight of 0 (can never be drawn).
+                for weight, n in zip(weights, range(min_value, max_value + 1)):
+                    if weight == 0:
+                        continue
+                    yield n
         else:
-            # skip any values with a corresponding weight of 0 (can never be drawn).
-            for weight, n in zip(weights, range(min_value, max_value + 1)):
-                if weight == 0:
-                    continue
-                yield n
+            # hard case: only one bound was specified. Here we probe either upwards
+            # or downwards with our full 128 bit generation, but only half of these
+            # (plus one for the case of generating zero) result in a probe in the
+            # direction we want. ((2**128 - 1) // 2) + 1 == a range of 2 ** 127.
+            #
+            # strictly speaking, I think this is not actually true: if
+            # max_value > shrink_towards then our range is ((-2**127) + 1, max_value),
+            # and it only narrows when max_value < shrink_towards. But it
+            # really doesn't matter for this case because (even half) unbounded
+            # integers generation is hit extremely rarely.
+            assert (min_value is None) ^ (max_value is None)
+            if min_value is None:
+                yield from range(max_value - (2**127) + 1, max_value)
+            else:
+                assert max_value is None
+                yield from range(min_value, min_value + (2**127) - 1)
 
     if ir_type == "boolean":
         p = kwargs["p"]


Test patch:
diff --git a/hypothesis-python/tests/conjecture/test_ir.py b/hypothesis-python/tests/conjecture/test_ir.py
--- a/hypothesis-python/tests/conjecture/test_ir.py
+++ b/hypothesis-python/tests/conjecture/test_ir.py
@@ -255,7 +255,6 @@ def test_draw_string_single_interval_with_equal_bounds(s, n):
             "min_value": 1,
             "max_value": 2,
             "weights": [0, 1],
-            "smallest_nonzero_magnitude": SMALLEST_SUBNORMAL,
         },
     )
 )
@@ -273,6 +272,19 @@ def test_compute_max_children_and_all_children_agree(ir_type_and_kwargs):
     assert len(list(all_children(ir_type, kwargs))) == max_children
 
 
+# it's very hard to test that unbounded integer ranges agree with
+# compute_max_children, because they by necessity require iterating over 2**127
+# or more elements. We do the not great approximation of checking just the first
+# element is what we expect.
+@pytest.mark.parametrize(
+    "min_value, max_value, first",
+    [(None, None, -(2**127) + 1), (None, 42, (-(2**127) + 1) + 42), (42, None, 42)],
+)
+def test_compute_max_children_unbounded_integer_ranges(min_value, max_value, first):
+    kwargs = {"min_value": min_value, "max_value": max_value, "weights": None}
+    assert first == next(all_children("integer", kwargs))
+
+
 @given(st.randoms())
 def test_ir_nodes(random):
     data = fresh_data(random=random)