Skip to content

Commit df78fb8

Browse files
DRMacIverclaude
andcommitted
Cache _natural_simpler_chars and extend candidates with all chars
Per review: - ``.update()`` the candidate set with every character of the case-mapped and decomposed forms instead of only single-character results. This lets e.g. ``ß`` shrink to ``s`` via ``casefold() == "ss"``, and adds ``i`` (alongside ``f``) as a candidate when shrinking the ``fi`` ligature. - Wrap the helper in ``lru_cache(maxsize=4096)`` since it gets called in tight loops from the shrink pass's chooser predicates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 08f6bbb commit df78fb8

2 files changed

Lines changed: 13 additions & 4 deletions

File tree

hypothesis-python/src/hypothesis/internal/conjecture/shrinker.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from collections import defaultdict
1414
from collections.abc import Callable, Sequence
1515
from dataclasses import dataclass
16+
from functools import lru_cache
1617
from typing import (
1718
TYPE_CHECKING,
1819
Any,
@@ -90,20 +91,24 @@ def sort_key(nodes: Sequence[ChoiceNode]) -> tuple[int, tuple[int, ...]]:
9091
)
9192

9293

94+
@lru_cache(maxsize=4096)
9395
def _natural_simpler_chars(c, intervals):
9496
"""Return single-char replacements for ``c`` derived from natural text
9597
transformations - case mapping (upper, lower, casefold) and unicode
96-
decomposition (NFD, NFKD - taking just the base character).
98+
decomposition (NFD, NFKD). We take each individual character of the
99+
transformed form so that e.g. ``ß`` can shrink to ``s`` via casefold
100+
even though the full case-folded form is two characters.
97101
98102
Only candidates which are in ``intervals`` and which have a strictly
99103
smaller index in shrink order than ``c`` are returned, sorted by that
100104
shrink-order index. Callers must pass a single character that is itself
101105
in ``intervals``.
102106
"""
103-
candidates = {unicodedata.normalize(form, c)[0] for form in ("NFKD", "NFD")}
107+
candidates: set[str] = set()
108+
for form in ("NFKD", "NFD"):
109+
candidates.update(unicodedata.normalize(form, c))
104110
for transformed in (c.upper(), c.lower(), c.casefold()):
105-
if len(transformed) == 1:
106-
candidates.add(transformed)
111+
candidates.update(transformed)
107112
candidates.discard(c)
108113
original_idx = intervals.index_from_char_in_shrink_order(c)
109114
result = sorted(

hypothesis-python/tests/conjecture/test_shrinker.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,10 @@ def shrinker(data: ConjectureData):
804804
("B", "B"),
805805
# Ligatures decompose to their first base char under NFKD.
806806
("fi", "f"),
807+
# Case mapping that produces multiple chars: ``ß.casefold() == "ss"``,
808+
# and we accept any of the individual characters of the case-mapped
809+
# form as a single-char replacement.
810+
("ß", "s"),
807811
# Case mapping: uppercase has a smaller shrink-order index than
808812
# lowercase, so we can swap a→A.
809813
("a", "A"),

0 commit comments

Comments
 (0)