_sre: fix Match.expand to use _compile_template (#8229) (#8256)

Match.expand delegated to re._expand, which CPython removed in 3.12
during the template-expansion refactor. With the 3.14 stdlib that
helper no longer exists, so expand raised AttributeError.

Route expand through the compiled-template path
(Template::compile -> re._compile_template) and share the
template-filling logic with Pattern.sub via a Match::expand_template
helper. Each caller still compiles the template once, so sub keeps
its no-recompile-per-match fast path.

Remove the expectedFailure marker on test_re.test_expand.

Assisted-by: Claude Code:claude-opus-4-8
This commit is contained in:
YujinBae authored and GitHub committed 2026-07-13 14:10:23 +09:00
1 parent b1cfa575c4
commit af0fa212cc
2 files changed
+34 -14

No files matched your search

-1
View File
@@ -726,7 +726,6 @@ class ReTests(unittest.TestCase):
'first second').groupdict(),
{'first':'first', 'second':'second'})
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_expand(self):
self.assertEqual(re.match("(?P<first>first) (?P<second>second)",
"first second")
+34 -13
View File
@@ -471,15 +471,7 @@ mod _sre {
}
FilterType::Template(template) => {
let m = Match::new(&mut iter.state, zelf.clone(), string.clone());
// template.expand(m)?
// let mut list = vec![template.literal.clone()];
sub_list.push(template.literal.clone());
for (index, literal) in template.items.iter().cloned() {
if let Some(item) = m.get_slice(index, s, vm) {
sub_list.push(item);
}
sub_list.push(literal);
}
m.expand_template(template, s, &mut sub_list, vm);
}
};
@@ -702,10 +694,19 @@ mod _sre {
}
#[pymethod]
fn expand(zelf: PyRef<Self>, template: PyStrRef, vm: &VirtualMachine) -> PyResult {
let re = vm.import("re", 0)?;
let func = re.get_attr("_expand", vm)?;
func.call((zelf.pattern.clone(), zelf, template), vm)
fn expand(zelf: PyRef<Self>, template: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let template = Template::compile(zelf.pattern.clone(), template, vm)?;
with_sre_str!(zelf.pattern, &zelf.string, vm, |s| {
let mut list: Vec<PyObjectRef> = Vec::new();
zelf.expand_template(&template, s, &mut list, vm);
let join_type: PyObjectRef = if zelf.pattern.isbytes {
vm.ctx.new_bytes(vec![]).into()
} else {
vm.ctx.new_str(ascii!("")).into()
};
vm.call_method(&join_type, "join", (PyList::from(list).into_pyobject(vm),))
})
}
#[pymethod]
@@ -820,6 +821,26 @@ mod _sre {
Some(str_drive.slice(start as usize, end as usize, vm))
}
/// Expand an already-compiled template against this match, appending the
/// resulting literal/group segments to `list`. Shared by `expand` and
/// `Pattern.sub` so the template-filling logic lives in one place; the
/// caller is responsible for compiling the template (once) beforehand.
fn expand_template<S: SreStr>(
&self,
template: &Template,
str_drive: S,
list: &mut Vec<PyObjectRef>,
vm: &VirtualMachine,
) {
list.push(template.literal.clone());
for (index, literal) in template.items.iter().cloned() {
if let Some(item) = self.get_slice(index, str_drive, vm) {
list.push(item);
}
list.push(literal);
}
}
#[pyclassmethod]
fn __class_getitem__(
cls: PyTypeRef,