Skip to content

Conversation

@hvitved
Copy link
Contributor

@hvitved hvitved commented Feb 10, 2026

The first commit simplifies inferMethodCallTypeSelf by making it less recursive (we only need to account for implicit borrows once).

The second commit replaces some multi-group regexp capture calls with something that doesn't use multiple capture groups, which means avoiding fan-out:

Before:
Pipeline standard for TypeInference::inferMethodCallTypeSelf/4#97a10fa1@4d7a5m5w was evaluated in 425 iterations totaling 196ms (delta sizes total: 100948).
        138259   ~1%    {5} r1 = SCAN `TypeInference::inferMethodCallType0/5#83d55d13#prev_delta` OUTPUT In.1, In.2, In.3, In.4, In.5
         68873   ~0%    {5}    | JOIN WITH `FunctionType::FunctionPosition.isSelf/0#dispred#9e84d302` ON FIRST 1 OUTPUT Lhs.1, Lhs.2, Lhs.3, Lhs.4, _
         68873   ~0%    {5}    | REWRITE WITH Out.4 := "^(.*);(.*)$"
        137746   ~0%    {7}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.1,Lhs.4
        137746   ~1%    {8}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, In.6, _
                        {7}    | REWRITE WITH Tmp.7 := 1, TEST InOut.5 = Tmp.7 KEEPING 7
         68873   ~0%    {6}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.6, _
         68873   ~0%    {6}    | REWRITE WITH Out.5 := "^(.*);(.*)$"
        137746   ~1%    {8}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.1,Lhs.5
        137746   ~1%    {9}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, In.6, In.7, _
                        {8}    | REWRITE WITH Tmp.8 := 2, TEST InOut.6 = Tmp.8 KEEPING 8
         68873   ~0%    {5}    | SCAN OUTPUT In.7, In.0, In.2, In.3, In.4
         68873   ~1%    {5}    | JOIN WITH `TypeInference::BorrowKind.toString/0#dispred#30767ca7_10#join_rhs` ON FIRST 1 OUTPUT Lhs.1, Lhs.4, Rhs.1, Lhs.2, Lhs.3
                    
        100938   ~0%    {5} r2 = SCAN `TypeInference::inferMethodCallTypeSelf/4#97a10fa1#prev_delta` OUTPUT In.2, In.0, In.1, In.3, In.4
                    
         35632   ~0%    {7} r3 = JOIN r2 WITH `_Type::DataType.getPositionalTypeParameter/1#dispred#3bf49cbe_102#join_rhs_TypeInference::BorrowKind__#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Rhs.2, Lhs.1, Lhs.2, Lhs.3, Lhs.4, _
         35632   ~1%    {7}    | REWRITE WITH Out.6 := "^([0-9]+)\\.(.*)$"
         47100   ~1%    {9}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.4,Lhs.6
         47100   ~0%    {10}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, In.6, In.7, In.8, _
                        {9}    | REWRITE WITH Tmp.9 := 2, TEST InOut.7 = Tmp.9 KEEPING 9
         23550   ~0%    {8}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, In.8, _
         23550   ~1%    {8}    | REWRITE WITH Out.7 := "^([0-9]+)\\.(.*)$"
         47100   ~0%    {10}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.4,Lhs.7
         47100   ~1%    {11}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, In.6, In.7, In.8, In.9, _
                        {10}    | REWRITE WITH Tmp.10 := 1, TEST InOut.8 = Tmp.10 KEEPING 10
         23550   ~0%    {7}    | SCAN OUTPUT In.1, In.9, In.0, In.2, In.3, In.5, In.6
         23550   ~1%    {5}    | JOIN WITH `UnboundList::Make<Locations::Location,TypeInference::M1::UnboundListInput>::encode/1#1241f37a` ON FIRST 2 OUTPUT Lhs.3, Lhs.4, Lhs.2, Lhs.6, Lhs.5
                    
         65306  ~80%    {4} r4 = JOIN r2 WITH num#TypeInference::TNoBorrowKind#f0923795 ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, _
         65306  ~79%    {4}    | REWRITE WITH Out.3 := "^([0-9]+)\\.(.*)$"
          7308  ~63%    {6}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.2,Lhs.3
          7308  ~64%    {7}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, _
                        {6}    | REWRITE WITH Tmp.6 := 2, TEST InOut.4 = Tmp.6 KEEPING 6
          3654  ~64%    {5}    | SCAN OUTPUT In.0, In.1, In.2, In.5, _
          3654  ~63%    {5}    | REWRITE WITH Out.4 := "^([0-9]+)\\.(.*)$"
          7308  ~64%    {7}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.2,Lhs.4
          7308  ~64%    {8}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, In.6, _
                        {7}    | REWRITE WITH Tmp.7 := 1, TEST InOut.5 = Tmp.7 KEEPING 7
          3654  ~64%    {4}    | SCAN OUTPUT In.6, In.0, In.1, In.3
          2720   ~0%    {4}    | JOIN WITH `UnboundList::Make<Locations::Location,DerefChain::UnboundListInput>::encode/1#3adb0d93_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.3
          2720   ~0%    {4}    | JOIN WITH DerefChain::DerefImplItemNode#858b9352 ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3
          6662   ~0%    {6}    | JOIN WITH `TypeMention::resolveImplSelfTypeAt/2#fb7c51d3` ON FIRST 1 OUTPUT Lhs.0, Rhs.1, Rhs.2, Lhs.1, Lhs.2, Lhs.3
          6662   ~0%    {5}    | JOIN WITH `TypeMention::resolveImplSelfTypeAt/2#fb7c51d3` ON FIRST 3 OUTPUT Lhs.2, Lhs.3, Lhs.4, Lhs.5, Lhs.1
                        {5}    | AND NOT Type::TypeParameter#9c366c86(FIRST 1)
          2720   ~0%    {5}    | SCAN OUTPUT In.2, In.3, In.1, In.4, In.0
                    
         65306   ~0%    {6} r5 = JOIN r2 WITH num#TypeInference::TNoBorrowKind#f0923795 ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3, Lhs.4, _
         65306   ~1%    {6}    | REWRITE WITH Out.5 := "^([0-9]+)\\.(.*)$"
          7308   ~0%    {8}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.2,Lhs.5
          7308   ~0%    {9}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, In.6, In.7, _
                        {8}    | REWRITE WITH Tmp.8 := 2, TEST InOut.6 = Tmp.8 KEEPING 8
          3654   ~0%    {7}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.7, _
          3654   ~0%    {7}    | REWRITE WITH Out.6 := "^([0-9]+)\\.(.*)$"
          7308   ~0%    {9}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.2,Lhs.6
          7308   ~0%    {10}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, In.6, In.7, In.8, _
                        {9}    | REWRITE WITH Tmp.9 := 1, TEST InOut.7 = Tmp.9 KEEPING 9
          3654   ~0%    {6}    | SCAN OUTPUT In.8, In.0, In.1, In.3, In.4, In.5
          3654   ~0%    {6}    | JOIN WITH `UnboundList::Make<Locations::Location,DerefChain::UnboundListInput>::encode/1#3adb0d93_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.3, Lhs.4, Lhs.5
          3654   ~0%    {6}    | JOIN WITH DerefChain::DerefImplItemNode#858b9352 ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3, Lhs.4, Lhs.5
          8831   ~1%    {7}    | JOIN WITH `TypeMention::resolveImplSelfTypeAt/2#fb7c51d3` ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3, Lhs.5, Rhs.1, Lhs.4
          8581   ~0%    {8}    | JOIN WITH `DerefChain::DerefImplItemNode.targetHasTypeParameterAt/1#cacd5683` ON FIRST 1 OUTPUT Lhs.1, Lhs.2, Lhs.4, Lhs.5, Lhs.6, _, Rhs.1, Lhs.3
                        {6}    | REWRITE WITH Tmp.5 := "", Out.5 := InverseAppend(In.6,Tmp.5,In.7) KEEPING 6
          6867   ~0%    {10}    | SCAN OUTPUT In.0, In.1, In.2, In.4, _, _, In.3, In.5, _, _
                        {6}    | REWRITE WITH Out.4 := (In.6 ++ In.7), Tmp.5 := (In.6 ++ In.7), Tmp.8 := "[0-9]+", Tmp.9 := "", Out.5 := regexpReplaceAll(Tmp.5,Tmp.8,Tmp.9) KEEPING 6
          6867   ~0%    {8}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, _, In.5, _
                        {6}    | REWRITE WITH Out.5 := length(In.6), Tmp.7 := 10, TEST Out.5 <= Tmp.7 KEEPING 6
          6867   ~1%    {5}    | SCAN OUTPUT In.1, In.2, In.0, In.4, In.3
                    
        102010   ~1%    {5} r6 = r1 UNION r3 UNION r4 UNION r5
        100948   ~1%    {5}    | AND NOT `TypeInference::inferMethodCallTypeSelf/4#97a10fa1#prev`(FIRST 5)
                        return r6
After:
Pipeline standard for TypeInference::inferMethodCallTypeSelf/3#82ff8871@68980n7w was evaluated in 425 iterations totaling 62ms (delta sizes total: 65320).
        138259   ~1%    {5} r1 = SCAN `TypeInference::inferMethodCallType0/5#83d55d13#prev_delta` OUTPUT In.1, In.2, In.3, In.4, In.5
         68873   ~0%    {5}    | JOIN WITH `FunctionType::FunctionPosition.isSelf/0#dispred#9e84d302` ON FIRST 1 OUTPUT Lhs.1, Lhs.2, Lhs.3, Lhs.4, _
         68873   ~1%    {5}    | REWRITE WITH Out.4 := ";"
         68873   ~1%    {6}    | JOIN WITH PRIMITIVE indexOf#bbf ON Lhs.1,Lhs.4
         68873   ~1%    {7}    | SCAN OUTPUT In.0, In.1, In.2, In.3, _, _, In.5
                        {6}    | REWRITE WITH Tmp.4 := 1, Out.4 := (In.6 + Tmp.4), Out.5 := prefix(InOut.1,In.6) KEEPING 6
         68873   ~0%    {7}    | SCAN OUTPUT In.0, In.2, In.3, In.5, _, In.1, In.4
                        {5}    | REWRITE WITH Out.4 := suffix(In.5,In.6) KEEPING 5
         68873   ~0%    {5}    | SCAN OUTPUT In.4, In.0, In.1, In.2, In.3
         68873   ~1%    {5}    | JOIN WITH `TypeInference::BorrowKind.toString/0#dispred#30767ca7_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.3, Lhs.4
                    
         33241   ~1%    {4} r2 = JOIN r1 WITH num#TypeInference::TNoBorrowKind#f0923795 ON FIRST 1 OUTPUT Lhs.1, Lhs.4, Lhs.2, Lhs.3
                    
         65306  ~79%    {3} r3 = SCAN `TypeInference::inferMethodCallTypeSelf/3#82ff8871#prev_delta` OUTPUT In.0, In.1, _
         65306  ~80%    {3}    | REWRITE WITH Out.2 := "^([0-9]+)\\..*$"
          3654  ~63%    {5}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.1,Lhs.2
          3654  ~63%    {6}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, _
                        {5}    | REWRITE WITH Tmp.5 := 1, TEST InOut.3 = Tmp.5 KEEPING 5
          3654  ~62%    {3}    | SCAN OUTPUT In.4, In.0, In.1
          2720   ~0%    {4}    | JOIN WITH `UnboundList::Make<Locations::Location,DerefChain::UnboundListInput>::encode/1#3adb0d93_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.0
          2720   ~0%    {4}    | JOIN WITH DerefChain::DerefImplItemNode#858b9352 ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3
          6662   ~0%    {8}    | JOIN WITH `TypeMention::resolveImplSelfTypeAt/2#fb7c51d3` ON FIRST 1 OUTPUT Lhs.1, Lhs.0, Rhs.1, Rhs.2, _, Lhs.2, Lhs.3, _
                        {5}    | REWRITE WITH Tmp.4 := length(In.6), Tmp.7 := 1, Tmp.4 := (Tmp.4 + Tmp.7), Out.4 := suffix(In.5,Tmp.4) KEEPING 5
          6662   ~0%    {5}    | SCAN OUTPUT In.1, In.2, In.3, In.0, In.4
          6662   ~1%    {4}    | JOIN WITH `TypeMention::resolveImplSelfTypeAt/2#fb7c51d3` ON FIRST 3 OUTPUT Lhs.2, Lhs.3, Lhs.4, Lhs.1
                        {4}    | AND NOT Type::TypeParameter#9c366c86(FIRST 1)
          2720   ~0%    {4}    | SCAN OUTPUT In.1, In.2, In.3, In.0
                    
         35632   ~0%    {6} r4 = JOIN r1 WITH `TypeInference::BorrowKind.getRefType/0#dispred#1f6dc1a8` ON FIRST 1 OUTPUT Rhs.1, _, Lhs.1, Lhs.2, Lhs.3, Lhs.4
         35632   ~0%    {6}    | REWRITE WITH Out.1 := 0
         35632   ~0%    {5}    | JOIN WITH `Type::DataType.getPositionalTypeParameter/1#dispred#3bf49cbe` ON FIRST 2 OUTPUT Rhs.2, Lhs.2, Lhs.3, Lhs.4, Lhs.5
         35632   ~0%    {6}    | JOIN WITH `TypeInference::TypePath::singleton/1#cf421a09` ON FIRST 1 OUTPUT Lhs.1, Lhs.3, Lhs.4, _, Rhs.1, Lhs.2
                        {4}    | REWRITE WITH Tmp.3 := "", Out.3 := InverseAppend(In.4,Tmp.3,In.5) KEEPING 4
         23550   ~1%    {4}    | SCAN OUTPUT In.0, In.2, In.3, In.1
                    
         65306   ~1%    {5} r5 = SCAN `TypeInference::inferMethodCallTypeSelf/3#82ff8871#prev_delta` OUTPUT In.0, In.1, In.2, In.3, _
         65306   ~1%    {5}    | REWRITE WITH Out.4 := "^([0-9]+)\\..*$"
          3654   ~0%    {7}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.1,Lhs.4
          3654   ~1%    {8}    | SCAN OUTPUT In.0, In.1, In.2, In.3, In.4, In.5, In.6, _
                        {7}    | REWRITE WITH Tmp.7 := 1, TEST InOut.5 = Tmp.7 KEEPING 7
          3654   ~0%    {5}    | SCAN OUTPUT In.6, In.0, In.1, In.2, In.3
          3654   ~0%    {6}    | JOIN WITH `UnboundList::Make<Locations::Location,DerefChain::UnboundListInput>::encode/1#3adb0d93_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.3, Lhs.4, Lhs.0
          3654   ~0%    {6}    | JOIN WITH DerefChain::DerefImplItemNode#858b9352 ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3, Lhs.4, Lhs.5
          8831   ~0%    {9}    | JOIN WITH `TypeMention::resolveImplSelfTypeAt/2#fb7c51d3` ON FIRST 1 OUTPUT Lhs.1, Lhs.3, Lhs.4, Lhs.0, Rhs.1, _, Lhs.2, Lhs.5, _
                        {6}    | REWRITE WITH Tmp.5 := length(In.7), Tmp.8 := 1, Tmp.5 := (Tmp.5 + Tmp.8), Out.5 := suffix(In.6,Tmp.5) KEEPING 6
          8831   ~0%    {6}    | SCAN OUTPUT In.3, In.0, In.1, In.4, In.5, In.2
          8581   ~1%    {7}    | JOIN WITH `DerefChain::DerefImplItemNode.targetHasTypeParameterAt/1#cacd5683` ON FIRST 1 OUTPUT Lhs.1, Lhs.3, Lhs.4, Lhs.5, _, Rhs.1, Lhs.2
                        {5}    | REWRITE WITH Tmp.4 := "", Out.4 := InverseAppend(In.5,Tmp.4,In.6) KEEPING 5
          6867   ~0%    {9}    | SCAN OUTPUT In.0, In.2, In.3, _, _, In.1, In.4, _, _
                        {5}    | REWRITE WITH Out.3 := (In.5 ++ In.6), Tmp.4 := (In.5 ++ In.6), Tmp.7 := "[0-9]+", Tmp.8 := "", Out.4 := regexpReplaceAll(Tmp.4,Tmp.7,Tmp.8) KEEPING 5
          6867   ~0%    {7}    | SCAN OUTPUT In.0, In.1, In.2, In.3, _, In.4, _
                        {5}    | REWRITE WITH Out.4 := length(In.5), Tmp.6 := 10, TEST Out.4 <= Tmp.6 KEEPING 5
          6867   ~0%    {4}    | SCAN OUTPUT In.0, In.1, In.3, In.2
                    
         66378   ~1%    {4} r6 = r2 UNION r3 UNION r4 UNION r5
         65320   ~1%    {4}    | AND NOT `TypeInference::inferMethodCallTypeSelf/3#82ff8871#prev`(FIRST 4)
                        return r6

DCA shows a nice speedup, especially on mist-os.

@github-actions github-actions bot added the Rust Pull requests that update Rust code label Feb 10, 2026
@hvitved hvitved force-pushed the rust/type-inference-speedup branch from 1715a43 to 9ab93d5 Compare February 10, 2026 14:50
@hvitved hvitved force-pushed the rust/type-inference-speedup branch from 9ab93d5 to 49f24ca Compare February 10, 2026 15:12
@hvitved hvitved added the no-change-note-required This PR does not need a change note label Feb 11, 2026
@hvitved hvitved marked this pull request as ready for review February 11, 2026 09:11
@hvitved hvitved requested review from a team as code owners February 11, 2026 09:11
Copilot AI review requested due to automatic review settings February 11, 2026 09:11
@hvitved hvitved requested a review from paldepind February 11, 2026 09:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves performance of Rust method-call type inference by reducing recursion in inferMethodCallTypeSelf and avoiding multi-capture regex patterns that cause fan-out in evaluation.

Changes:

  • Refactors inferMethodCallTypeSelf to account for implicit borrows without repeated recursion and updates call sites accordingly.
  • Replaces regexpCapture usage with indexOf/prefix/suffix in decodeDerefChainBorrow to avoid multi-group capture fan-out.
  • Optimizes UnboundList deconstruction predicates to avoid multiple regex capture groups and adds a helper for string-length vs list-length.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
shared/util/codeql/util/UnboundList.qll Avoids multi-capture regexpCapture in list deconstruction and adds a helper for raw string length.
rust/ql/lib/codeql/rust/internal/typeinference/TypeInference.qll Refactors self-receiver type inference and replaces regex splitting with index-based splitting for performance.
Comments suppressed due to low confidence (1)

shared/util/codeql/util/UnboundList.qll:136

  • This comment should be capitalized (“Same remark …”) to match the surrounding documentation style.
      // same remark as above about not using multiple capture groups
      prefix = this.regexpCapture("^(|.+\\.)[0-9]+\\.$", 1) and

Comment on lines +122 to +126
// it is more efficient to not create a capture group for the suffix, since
// `regexpCapture` will then always join in both groups, only to afterwards filter
// based on the requested group (the group number is not part of the binding set
// of `regexpCapture`)
elem = this.regexpCapture("^([0-9]+)\\..*$", 1) and
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new explanatory comment should start with a capital letter and could be tightened for readability (it currently spans multiple lines and uses a long parenthetical). Consider rephrasing with shorter sentences and consistent terminology (e.g., backticking bindingset).

This issue also appears on line 135 of the same file.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@paldepind paldepind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// it is more efficient to not create a capture group for the suffix, since
// `regexpCapture` will then always join in both groups, only to afterwards filter
// based on the requested group (the group number is not part of the binding set
// of `regexpCapture`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this saying that each call to regexpCapture would produce both capture groups and in the first call the second capture group would be thrown away and vice versa for the second call? Or something else?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Here is a simple example

predicate p1(string a, string b, string c) {
  a = "b;c" and
  exists(string reg |
    reg = "^(.*);(.*)$" and
    b = a.regexpCapture(reg, 1) and
    c = a.regexpCapture(reg, 2)
  )
}

which generates the following RA:

[2026-02-10 15:54:01] (0s) Tuple counts for quickquery::p1#QuickEval#c8bd3cdf#query/3@b482101n after 2ms:
                      1 ~0%     {2} r1 = CONSTANT(unique string, unique string)["b;c","^(.*);(.*)$"]
                      2 ~0%     {4}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.0,Lhs.1
                      2 ~0%     {5}    | SCAN OUTPUT In.0, In.1, In.2, In.3 'c', _
                                {4}    | REWRITE WITH Tmp.4 := 2, TEST InOut.2 = Tmp.4 KEEPING 4
                      1 ~0%     {3}    | SCAN OUTPUT In.3 'c', _, _
                      1 ~0%     {3}    | REWRITE WITH Out.1 := "b;c", Out.2 := "^(.*);(.*)$"
                      2 ~0%     {5}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.1,Lhs.2
                      2 ~0%     {6}    | SCAN OUTPUT In.0 'c', In.1, In.2, In.3, In.4 'b', _
                                {5}    | REWRITE WITH Tmp.5 := 1, TEST InOut.3 = Tmp.5 KEEPING 5
                      1 ~0%     {3}    | SCAN OUTPUT _, In.4 'b', In.0 'c'
                      1 ~0%     {3}    | REWRITE WITH Out.0 'a' := "b;c"
                                return r1

The workaround

predicate p4(string a, string b, string c) {
  a = "b;c" and
  exists(string reg |
    reg = "^(.*);.*$" and
    b = a.regexpCapture(reg, 1) and
    c = a.suffix(b.length() + 1)
  )
}

with just a single capture group performs better:

[2026-02-10 15:57:35] (0s) Tuple counts for quickquery::p4#QuickEval#b3644a80#query/3@e8e0caoi after 0ms:
                      1 ~0%     {2} r1 = CONSTANT(unique string, unique string)["b;c","^(.*);.*$"]
                      1 ~0%     {4}    | JOIN WITH PRIMITIVE regexpCapture#bbff ON Lhs.0,Lhs.1
                      1 ~0%     {5}    | SCAN OUTPUT In.0, In.1, In.2, In.3 'b', _
                                {4}    | REWRITE WITH Tmp.4 := 1, TEST InOut.2 = Tmp.4 KEEPING 4
                      1 ~0%     {4}    | SCAN OUTPUT In.3 'b', _, _, _
                                {2}    | REWRITE WITH Tmp.1 := "b;c", Tmp.2 := length(InOut.0), Tmp.3 := 1, Tmp.2 := (Tmp.2 + Tmp.3), Out.1 'c' := suffix(Tmp.1,Tmp.2) KEEPING 2
                      1 ~0%     {3}    | SCAN OUTPUT _, In.0 'b', In.1 'c'
                      1 ~0%     {3}    | REWRITE WITH Out.0 'a' := "b;c"

@hvitved hvitved merged commit 36c3084 into github:main Feb 11, 2026
61 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-change-note-required This PR does not need a change note Rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants