Skip to content

v2026.7.23

Choose a tag to compare

@github-actions github-actions released this 06 Jul 21:32

The \p{…} arc — Unicode property classes land on all three surfaces, linear like everything else, with the
one contract subtlety they surfaced documented rather than papered over. And the front-page comparison table
finally splits RE2 from the rust crate.

\p{L}, \p{Lu}, \p{Nd}, the groups \p{L}..\p{C}, and \p{sc=Greek} / \p{Script=Latin} — with short
codes, the UCD long names (\p{Letter}), gc= / sc= prefixes, loose matching, the single-letter form \pL,
and negation \P{…} — now compile and match on the C++ engine, the Python binding, and the Rust crate. They run
as the same klass_cp code-point predicate as \w, so they are linear as everything else: \p{L}+ measures
identical to \w+, \p{Nd}+ to \d+. They compose inside a class ([\p{L}\d_], [^\p{L}], the double
negation [^\P{L}]) and fold under IGNORECASE by the ordinary class pipeline. Accepting \p{…} is a superset
of stdlib re, which rejects \p outright — it cannot break re-compatible code. In the Rust binding they run
natively (engine() reports Real); a binary property like \p{Alphabetic}, not yet tabulated, still delegates
via the fallback feature.

Verifying the icase fold surfaced one code point (two, with İ) where the engines legitimately differ: under
(?i), REAL folds the Turkish dotless/dotted I (U+0131 / U+0130) with I/i — Python re's equivalence via
str.upper/lower, verified against stdlib (re.fullmatch("(?i)I", "ı") is True) — whereas RE2 and the rust
crate use Unicode simple CaseFolding, which keeps ı apart. One code point, both engines correct for their
contract. It is general (not \p-specific), documented on both the divergences page and the Rust README, and the
differential fuzzer masks exactly that set (its ICASE_FOLD_DELTAS, computed by asking both engines), the twin
of the existing \w/\s mask.

The General_Category tables are built from, and exhaustively re-checked against, unicodedata; the Script tables
are parsed from a committed Unicode 16.0.0 Scripts.txt and cross-checked against the regex module — each a
permanent regen guard over all 1.1M scalars. The parse → merge → match path is proven equal to the rust regex
crate exhaustively (0 divergence, both Unicode 16.0.0). The whole feature cost +7.5% compile and 0 throughput.

The front-page table splits RE2 and the rust regex crate — the numbers outgrew the adjectives. RE2 is a
uniform 3–9× slower; the rust crate is mixed (REAL leads class scans and dense captures, the crate leads
straight-line literals and word-boundary, no-match is near parity — the per-line duel is in the benchmarks), and
std::regex's cell now quantifies the ReDoS gap (4.1 s where REAL takes 0.5 ms) instead of an adjective.