Repository: HypothesisWorks/hypothesis

Issue:
Fixes for pyright on strict mode
:sparkles: _**This is an old work account. Please reference @brandonchinn178 for all future communication**_ :sparkles:
<!-- updated by mention_personal_account_in_comments.py -->

---


Remove dead code
While studying the float machinery (I'm trying to make floats shrink in a similar fashion in Elm's test library), I figured out that the constant `SPECIAL_EXPONENTS` is not used anywhere. It can probably be removed. Only the `MAX_EXPONENT` seems to be special anyways (kept at the end of the new exponents ordering).


Discussion:
At a high level, we want users to be able to strictly type-check their code which uses Hypothesis, so I'll be happy to accept this PR.

On an implementation level, I'd like to have a regression test to stop us from breaking it accidentally (like we did with `--junit-xml` 😅).  Happy to leave the details of that up to you, since unlike @rsokl I haven't used `pyright` much.


Resolving patch:
diff --git a/hypothesis-python/src/hypothesis/core.py b/hypothesis-python/src/hypothesis/core.py
--- a/hypothesis-python/src/hypothesis/core.py
+++ b/hypothesis-python/src/hypothesis/core.py
@@ -971,8 +971,8 @@ def fuzz_one_input(
 
 
 def given(
-    *_given_arguments: Union[SearchStrategy, InferType],
-    **_given_kwargs: Union[SearchStrategy, InferType],
+    *_given_arguments: Union[SearchStrategy[Ex], InferType],
+    **_given_kwargs: Union[SearchStrategy[Ex], InferType],
 ) -> Callable[
     [Callable[..., Union[None, Coroutine[Any, Any, None]]]], Callable[..., None]
 ]:
diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/floats.py b/hypothesis-python/src/hypothesis/internal/conjecture/floats.py
--- a/hypothesis-python/src/hypothesis/internal/conjecture/floats.py
+++ b/hypothesis-python/src/hypothesis/internal/conjecture/floats.py
@@ -76,8 +76,6 @@
 
 MAX_EXPONENT = 0x7FF
 
-SPECIAL_EXPONENTS = (0, MAX_EXPONENT)
-
 BIAS = 1023
 MAX_POSITIVE_EXPONENT = MAX_EXPONENT - 1 - BIAS
 
diff --git a/tooling/src/hypothesistooling/__main__.py b/tooling/src/hypothesistooling/__main__.py
--- a/tooling/src/hypothesistooling/__main__.py
+++ b/tooling/src/hypothesistooling/__main__.py
@@ -486,12 +486,15 @@ def check_examples3():
 
 
 @task()
-def check_whole_repo_tests():
+def check_whole_repo_tests(*args):
     install.ensure_shellcheck()
     subprocess.check_call(
         [sys.executable, "-m", "pip", "install", "--upgrade", hp.HYPOTHESIS_PYTHON]
     )
-    subprocess.check_call([sys.executable, "-m", "pytest", tools.REPO_TESTS])
+
+    if not args:
+        args = [tools.REPO_TESTS]
+    subprocess.check_call([sys.executable, "-m", "pytest", *args])
 
 
 @task()


Test patch:
diff --git a/whole-repo-tests/test_type_hints.py b/whole-repo-tests/test_mypy.py
similarity index 97%
rename from whole-repo-tests/test_type_hints.py
rename to whole-repo-tests/test_mypy.py
--- a/whole-repo-tests/test_type_hints.py
+++ b/whole-repo-tests/test_mypy.py
@@ -21,6 +21,14 @@ def test_mypy_passes_on_hypothesis():
     pip_tool("mypy", PYTHON_SRC)
 
 
+@pytest.mark.skip(
+    reason="Hypothesis type-annotates the public API as a convenience for users, "
+    "but strict checks for our internals would be a net drag on productivity."
+)
+def test_mypy_passes_on_hypothesis_strict():
+    pip_tool("mypy", "--strict", PYTHON_SRC)
+
+
 def get_mypy_output(fname, *extra_args):
     return subprocess.Popen(
         [tool_path("mypy"), *extra_args, fname],
diff --git a/whole-repo-tests/test_pyright.py b/whole-repo-tests/test_pyright.py
new file mode 100644
--- /dev/null
+++ b/whole-repo-tests/test_pyright.py
@@ -0,0 +1,78 @@
+# This file is part of Hypothesis, which may be found at
+# https://github.com/HypothesisWorks/hypothesis/
+#
+# Copyright the Hypothesis Authors.
+# Individual contributors are listed in AUTHORS.rst and the git log.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public License,
+# v. 2.0. If a copy of the MPL was not distributed with this file, You can
+# obtain one at https://mozilla.org/MPL/2.0/.
+
+from __future__ import annotations
+
+import json
+import subprocess
+import textwrap
+from pathlib import Path
+from typing import Any
+
+import pytest
+
+from hypothesistooling.projects.hypothesispython import HYPOTHESIS_PYTHON, PYTHON_SRC
+from hypothesistooling.scripts import pip_tool, tool_path
+
+
+@pytest.mark.skip(
+    reason="Hypothesis type-annotates the public API as a convenience for users, "
+    "but strict checks for our internals would be a net drag on productivity."
+)
+def test_pyright_passes_on_hypothesis():
+    pip_tool("pyright", "--project", HYPOTHESIS_PYTHON)
+
+
+def test_pyright_passes_on_basic_test(tmp_path: Path):
+    file = tmp_path / "test.py"
+    file.write_text(
+        textwrap.dedent(
+            """
+            import hypothesis
+            import hypothesis.strategies as st
+
+            @hypothesis.given(x=st.text())
+            def test_foo(x: str):
+                assert x == x
+
+            from hypothesis import given
+            from hypothesis.strategies import text
+
+            @given(x=text())
+            def test_bar(x: str):
+                assert x == x
+            """
+        )
+    )
+    _write_config(tmp_path, {"typeCheckingMode": "strict"})
+    assert _get_pyright_errors(file) == []
+
+
+# ---------- Helpers for running pyright ---------- #
+
+
+def _get_pyright_output(file: Path) -> dict[str, Any]:
+    proc = subprocess.run(
+        [tool_path("pyright"), "--outputjson"],
+        cwd=file.parent,
+        encoding="utf-8",
+        universal_newlines=True,
+        capture_output=True,
+    )
+    return json.loads(proc.stdout)
+
+
+def _get_pyright_errors(file: Path) -> list[dict[str, Any]]:
+    return _get_pyright_output(file)["generalDiagnostics"]
+
+
+def _write_config(config_dir: Path, data: dict[str, Any] | None = None):
+    config = {"extraPaths": [PYTHON_SRC], **(data or {})}
+    (config_dir / "pyrightconfig.json").write_text(json.dumps(config))