Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions crates/vm/src/stdlib/_sre.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,15 +452,25 @@ mod _sre {
let mut match_list: Vec<PyObjectRef> = Vec::new();
let mut iter = SearchIter { req, state };

// What a group that took no part in the match is reported as.
// `findall` hands back the matched text rather than a match
// object, so the stand-in has to be an empty value of the type
// the pattern works on. `Match.groups` still reports `None` and
// is not affected by this.
let empty: PyObjectRef = if zelf.isbytes {
vm.ctx.new_bytes(vec![]).into()
} else {
vm.ctx.new_str(ascii!("")).into()
};

while iter.next().is_some() {
let m = Match::new(&mut iter.state, zelf.clone(), string_args.string.clone());

let item = if zelf.groups == 0 || zelf.groups == 1 {
m.get_slice(zelf.groups, s, vm)
.unwrap_or_else(|| vm.ctx.none())
.unwrap_or_else(|| empty.clone())
} else {
m.groups(OptionalArg::Present(vm.ctx.new_str(ascii!("")).into()), vm)?
.into()
m.groups(OptionalArg::Present(empty.clone()), vm)?.into()
};

match_list.push(item);
Expand Down
44 changes: 44 additions & 0 deletions extra_tests/snippets/stdlib_re.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,47 @@

# Combining characters; issue #7518
assert not re.match(r"\w", "\u0345"), r"\w should not match U+0345 (category Mn)"


def test_findall_group_that_did_not_participate():
# findall returns the matched text, not a match object, so a group that
# took no part in the match stands in as an empty value of the type the
# pattern works on. One group used to come back as None, and a bytes
# pattern used to mix str into its results.
assert re.findall(r"(a)?b", "b ab") == ["", "a"]
assert re.findall(r"(x)?", "a") == ["", ""]
assert re.findall(r"(a|b)?c", "c ac bc") == ["", "a", "b"]
assert re.findall(r"(?P<g>a)?b", "b ab") == ["", "a"]
assert re.compile(r"(a)?b").findall("b ab") == ["", "a"]

assert re.findall(rb"(a)?b", b"b ab") == [b"", b"a"]
assert re.findall(rb"(x)?", b"a") == [b"", b""]

# Two or more groups give a tuple per match, with the same stand-in.
assert re.findall(r"(a)|(b)", "ab") == [("a", ""), ("", "b")]
assert re.findall(rb"(a)|(b)", b"ab") == [(b"a", b""), (b"", b"b")]
assert re.findall(rb"(a)(b)?", b"a ab") == [(b"a", b""), (b"a", b"b")]

# The type is the pattern's, never the other one.
assert [type(x) for x in re.findall(r"(a)?b", "b ab")] == [str, str]
assert [type(x) for x in re.findall(rb"(a)?b", b"b ab")] == [bytes, bytes]
assert [type(y) for x in re.findall(rb"(a)|(b)", b"ab") for y in x] == [
bytes,
bytes,
bytes,
bytes,
]

# A group that does participate, and no group at all, are unchanged.
assert re.findall(r"(a)", "aa") == ["a", "a"]
assert re.findall(r"a", "aa") == ["a", "a"]
assert re.findall(rb"a", b"aa") == [b"a", b"a"]

# A match object still reports None, which is where the difference lies.
assert re.match(r"(a)?b", "b").groups() == (None,)
assert re.match(r"(a)?b", "b").group(1) is None
assert [m.groups() for m in re.finditer(r"(a)?b", "b ab")] == [(None,), ("a",)]
assert re.split(r"(a)|(b)", "xaybz") == ["x", "a", None, "y", None, "b", "z"]


test_findall_group_that_did_not_participate()
Loading