Skip to content

re.fullmatch with possessive_repeat patterns doesn't behave as expected #7183

Closed

Description

@wvmscs

Feature

for example:

re.fullmatch("([0-9]++(?:\\.[0-9]+)*+)", "10.20.3") is expected to match, and does with CPython 3.14.3 ,
return None whith Python 3.14.0alpha.

(heads/main:7f64acdd5, Feb 12 2026, 16:19:33)
[RustPython 0.4.0 with rustc 1.93.0 (254b59607 2026-01-19)]

Error in source code

The bug is in the crate sre_engine where the context.toplevel must be set to false after processing the opcodes POSSESSIVE_REPEAT and ATOMIC_GROUP

Corrections made

I would submit the correction in the proper form in a few hours.
here is a preview.

diff --git a/crates/sre_engine/src/engine.rs b/crates/sre_engine/src/engine.rs

index 36a7a9d48..2bdcaa2a3 100644
--- a/crates/sre_engine/src/engine.rs
+++ b/crates/sre_engine/src/engine.rs
@@ -469,8 +469,10 @@ fn _match<S: StrDrive>(req: &Request<'_, S>, state: &mut State, mut ctx: MatchCo
                     }
                     Jump::PossessiveRepeat1 => {
                         let min_count = ctx.peek_code(req, 2) as isize;
-                        if ctx.count < min_count {
-                            break 'context ctx.next_offset(4, Jump::PossessiveRepeat2);
+                        if ctx.count < min_count { // modified next.toplevel from herited to false
+                            let mut next = ctx.next_offset(4, Jump::PossessiveRepeat2);
+                            next.toplevel = false;
+                            break 'context next;
                         }
                         // zero match protection
                         ctx.cursor.position = usize::MAX;
@@ -494,7 +496,9 @@ fn _match<S: StrDrive>(req: &Request<'_, S>, state: &mut State, mut ctx: MatchCo
                         {
                             state.marks.push();
                             ctx.cursor = state.cursor;
-                            break 'context ctx.next_offset(4, Jump::PossessiveRepeat4);
+                            let mut next = ctx.next_offset(4, Jump::PossessiveRepeat4);
+                            next.toplevel = false; // modified next.toplevel from herited to false
+                            break 'context next;
                         }
                         ctx.cursor = state.cursor;
                         ctx.skip_code_from(req, 1);
@@ -832,7 +836,9 @@ fn _match<S: StrDrive>(req: &Request<'_, S>, state: &mut State, mut ctx: MatchCo
                         /* <ATOMIC_GROUP> <skip> pattern <SUCCESS> tail */
                         SreOpcode::ATOMIC_GROUP => {
                             state.cursor = ctx.cursor;
-                            break 'context ctx.next_offset(2, Jump::AtomicGroup1);
+                            let mut next_ctx = ctx.next_offset(2, Jump::AtomicGroup1);
+                            next_ctx.toplevel = false;  // modified next.toplevel from herited to false
+                            break 'context next_ctx;
                         }
                         /* <POSSESSIVE_REPEAT> <skip> <1=min> <2=max> pattern
                         <SUCCESS> tail */


diff --git a/crates/sre_engine/tests/tests.rs b/crates/sre_engine/tests/tests.rs
index 72a11c55b..fc5c14091 100644
--- a/crates/sre_engine/tests/tests.rs
+++ b/crates/sre_engine/tests/tests.rs
@@ -145,6 +145,18 @@ fn test_possessive_quantifier() {
     assert!(state.py_match(&req));
 }
 
+#[test]
+fn test_possessive_repeat_fullmatch() {
+    // pattern p = re.compile("([0-9]++(?:\.[0-9]+)*+)", re.I   )
+    // [INFO, 4, 0, 1, 4294967295, MARK, 0, POSSESSIVE_REPEAT_ONE, 10, 1, MAXREPEAT, IN, 5, RANGE, 48, 57, FAILURE, SUCCESS, POSSESSIVE_REPEAT, 16, 0, MAXREPEAT, LITERAL, 46, REPEAT_ONE, 10, 1, MAXREPEAT, IN, 5, RANGE, 48, 57, FAILURE, SUCCESS, SUCCESS, MARK, 1, SUCCESS]
+    // START GENERATED by generate_tests.py
+    #[rustfmt::skip] let p = Pattern { pattern: "([0-9]++(?:\\.[0-9]+)*+)", code: &[14, 4, 0, 1, 4294967295, 17, 0, 29, 10, 1, 4294967295, 13, 5, 22, 48, 57, 0, 1, 28, 16, 0, 4294967295, 16, 46, 24, 10, 1, 4294967295, 13, 5, 22, 48, 57, 0, 1, 1, 17, 1, 1] };       
+    // END GENERATED
+    let (mut req, mut state) = p.state("1.25.38");
+    req.match_all = true;
+    assert!(state.py_match(&req), "should match");
+}
+
 #[test]
 fn test_possessive_atomic_group() {
     // pattern p = re.compile('(?>x)++x')

Activity

  1. added
    C-compatA discrepancy between RustPython and CPython
    on Feb 17, 2026
  2. youknowone commented on Feb 17, 2026

    @youknowone
    Member

    @wvmscs Thank you so much! Could you please open a PR with your patch?

  3. wvmscs commented on Feb 17, 2026

    @wvmscs
    ContributorAuthor

    I'll do that asap.
    This bug is a killer for setuptools operations.

  4. added 4 commits that reference this issue on Feb 17, 2026
    5f7a0d5
    b741934
    298ba3d
    d3dff2e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-compatA discrepancy between RustPython and CPython

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions