You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Preserve externally compiled regex flags during backend selection - #151
Both native backends rebuild RegexPattern.regex.pattern without its compile-time flags, so installing an optional backend silently changes the results of a precompiled re.Pattern:
The flags are translated into global inline flags where translate_end_anchor() already rewrites the expression — including both variants produced when GitIgnoreSpecPattern splits its directory marker, and as bytes for a bytes expression. No pattern is recompiled to probe for flags.
Measured with google-re2 1.1.20251105 and python-hyperscan 0.9.1 (Hyperscan 5.4) through the APIs pathspec uses:
flag
translation
re.IGNORECASE, re.MULTILINE, re.DOTALL
prepended as (?ims); both engines accept them, and (?i) is indistinguishable from re2.Options(case_sensitive=False) / HS_FLAG_CASELESS
re.UNICODE
dropped. Neither engine accepts (?u), both match \w \d \s \b as ASCII regardless, and (?U) would be a trap: RE2 reads that letter as non-greedy while every str pattern carries re.UNICODE implicitly
re.ASCII
no prefix needed, for the same reason — except with re.IGNORECASE, where the engines fold Unicode that Python under re.A does not (^[a-z]+$ matches ſ)
re.VERBOSE, re.LOCALE, other bits
can't be expressed: RE2 rejects (?x) (invalid perl operator: (?x) and has no verbose option; Hyperscan's (?x) rejects a trailing # comment Python compiles (Unterminated comment.)
For flags that can't be expressed, None/best selects simple and an explicit native backend raises ValueError naming backend='simple', as before. Ordinary gitignore patterns keep their native backend.
This restores the flag, not Python's case-folding: İ/ı and Unicode \w still differ, as they already do today.
Tests cover the three flags and their combinations on all backends, the default backend, bytes expressions, the directory-marker split, the fallback and the explicit-backend error, and the helpers. The in-place-addition hardening from the first revision is dropped — Pull #159 landed it upstream.
Validation (both native libraries installed): python -m unittest discover -t . -s tests/ → 266 OK on Linux (py3.10) and Windows (py3.12), base 254; 8 of 8 sample patterns agree across the three backends (was 3 of 8); mypy and pyright report the same 11 and 8 errors as the unmodified base. README.rst documents the mapping and README-dist.rst is synced.
Thanks for finding this bug! I definitely overlooked the flags detail.
I don't think has_regex_flags() is the right approach. Instead of recompiling the pattern to find in-string global flags, and bailing out in re2/hyperscan when they're detected, the .flags should be prepended to the regex string as global flags in re2/hyperscan.
(?aiLmsux)
(One or more letters from the set 'a', 'i', 'L', 'm', 's', 'u', 'x'.) The group matches the empty string; the letters set the corresponding flags for the entire regular expression:
Agreed — dropped has_regex_flags() and the recompile probe. The native backends now prepend the compile flags as inline global flags at the same points where translate_end_anchor() rewrites the expression, including both variants from the GitIgnoreSpecPattern directory-marker split, and as bytes for a bytes expression.
Support, measured with what CI uses (google-re2 1.1.20251105, python-hyperscan 0.9.1 over Hyperscan 5.4) through the APIs pathspec calls:
(?i) (?m) (?s) — accepted by both; (?i) is indistinguishable from re2.Options(case_sensitive=False) and from HS_FLAG_CASELESS.
(?x) — Hyperscan only. RE2 rejects it (invalid perl operator: (?x) and has no verbose option; Hyperscan additionally rejects (?x)abc # comment (Unterminated comment.), which Python compiles.
(?a) (?u) (?L) — rejected by both. (?U) is accepted by RE2 but means non-greedy there, and every str pattern carries re.UNICODE implicitly — so it's dropped, as you said.
Mapping used: (?ims) prefix; re.UNICODE dropped; re.ASCII taken as already satisfied (both engines match \w \d \s \b as ASCII regardless) except combined with re.IGNORECASE, where the engines fold Unicode Python forbids under re.A — ^[a-z]+$ matches ſ; re.VERBOSE, re.LOCALE and unknown bits can't be expressed, so best uses simple and an explicit native backend raises, as before. VERBOSE falls back on both backends rather than Hyperscan-only — say the word if you'd rather split it.
Dropped the += hunk since Pull #159 landed it upstream, and renamed the test module to tests/test_08_regex_flags.py.
PathSpec([RegexPattern(re.compile('^Secret$', re.I | re.M | re.S), include=True)]) now matches 'x\nSECRET\ny' on all three backends (previously only "simple"), likewise for each flag alone, for bytes, and through the directory-marker split — 8 of 8 sample cases agree across backends, was 3 of 8. unittest discover: 266 OK on Linux (py3.10) and Windows (py3.12), base 254; mypy (11) and pyright (8) unchanged from base. This restores the flag, not Python's case-folding (İ/ı) or Unicode \w — both already differ today.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Both native backends rebuild
RegexPattern.regex.patternwithout its compile-time flags, so installing an optional backend silently changes the results of a precompiledre.Pattern:The flags are translated into global inline flags where
translate_end_anchor()already rewrites the expression — including both variants produced whenGitIgnoreSpecPatternsplits its directory marker, and asbytesfor a bytes expression. No pattern is recompiled to probe for flags.Measured with
google-re2 1.1.20251105andpython-hyperscan 0.9.1(Hyperscan 5.4) through the APIs pathspec uses:re.IGNORECASE,re.MULTILINE,re.DOTALL(?ims); both engines accept them, and(?i)is indistinguishable fromre2.Options(case_sensitive=False)/HS_FLAG_CASELESSre.UNICODE(?u), both match\w \d \s \bas ASCII regardless, and(?U)would be a trap: RE2 reads that letter as non-greedy while every str pattern carriesre.UNICODEimplicitlyre.ASCIIre.IGNORECASE, where the engines fold Unicode that Python underre.Adoes not (^[a-z]+$matchesſ)re.VERBOSE,re.LOCALE, other bits(?x)(invalid perl operator: (?x) and has no verbose option; Hyperscan's(?x)rejects a trailing#comment Python compiles (Unterminated comment.)For flags that can't be expressed,
None/bestselectssimpleand an explicit native backend raisesValueErrornamingbackend='simple', as before. Ordinary gitignore patterns keep their native backend.This restores the flag, not Python's case-folding:
İ/ıand Unicode\wstill differ, as they already do today.Tests cover the three flags and their combinations on all backends, the default backend, bytes expressions, the directory-marker split, the fallback and the explicit-backend error, and the helpers. The in-place-addition hardening from the first revision is dropped —
Pull #159landed it upstream.Validation (both native libraries installed):
python -m unittest discover -t . -s tests/→ 266 OK on Linux (py3.10) and Windows (py3.12), base 254; 8 of 8 sample patterns agree across the three backends (was 3 of 8);mypyandpyrightreport the same 11 and 8 errors as the unmodified base.README.rstdocuments the mapping andREADME-dist.rstis synced.