proj/portage:master commit in: lib/portage/tests/dep/, lib/portage/dep/

"Matt Turner" <[email protected]>
Newsgroups gmane.linux.gentoo.cvs
Message-ID <1785963928.600c6bc6c2ce8ddf650579b0790a1faadae0d9c5.mattst88@gentoo>
commit:     600c6bc6c2ce8ddf650579b0790a1faadae0d9c5
Author:     Matt Turner <mattst88 <AT> gentoo <DOT> org>
AuthorDate: Sun Aug  2 20:19:03 2026 +0000
Commit:     Matt Turner <mattst88 <AT> gentoo <DOT> org>
CommitDate: Wed Aug  5 21:05:28 2026 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=600c6bc6

dep: extend C parser fast path to flat=True

The use_reduce fast path previously excluded flat=True, so the
_flatten_atoms consumer (and any other flat caller) stayed on the
pure-Python path.  Support it by flattening the C parse tree.

The C parser preserves the raw group/any-of structure with USE
conditionals already evaluated and does not apply the non-flat
bracket/|| optimizations, so flat mode is a straight recursive flatten
of that tree: inline every sublist while keeping operator strings ("||")
and Atoms in order.  This reproduces use_reduce(flat=True) exactly,
including the repeated "||" tokens that nested any-of groups produce in
flat mode (which the non-flat path collapses).

Adds flat parity tests (groups, nested ||, active/inactive conditionals,
matchall, mixed) and a differential check over varied dep strings
confirms C and Python flat output are identical.

Profiled cProfile emerge -uDNp @world: _c_fast_use_reduce 34820 ->
39032 calls (the ~4.2k flat callers), _use_reduce_cached 64680 -> 61502.
The remaining Python-path use_reduce calls are the is_valid_flag
_select_atoms path, which the C fast path cannot serve.

Signed-off-by: Matt Turner <mattst88 <AT> gentoo.org>

 lib/portage/dep/__init__.py            | 20 +++++++++--
 lib/portage/tests/dep/test_c_parser.py | 66 ++++++++++++++++++++++++++++++++--
 2 files changed, 80 insertions(+), 6 deletions(-)

diff --git a/lib/portage/dep/__init__.py b/lib/portage/dep/__init__.py
index 07c270e65..ef345e807 100644
--- a/lib/portage/dep/__init__.py
+++ b/lib/portage/dep/__init__.py
@@ -200,9 +200,24 @@ def _c_normalize_seq(seq, empty_always_true, under_anyof=False):
     return out
 
 
-def _c_fast_use_reduce(depstr, uselist, matchall, eapi):
+def _c_flatten_result(items, out, eapi, eapi_attrs, uselist, matchall):
+    """Recursively flatten the C parse tree into use_reduce(flat=True) form.
+    Nested || groups produce repeated '||' tokens, matching the Python path."""
+    for item in items:
+        if isinstance(item, str):
+            out.append(item)
+        elif isinstance(item, list):
+            _c_flatten_result(item, out, eapi, eapi_attrs, uselist, matchall)
+        else:
+            out.append(_c_atom_from_c(item, eapi, eapi_attrs, uselist, matchall))
+    return out
+
+
+def _c_fast_use_reduce(depstr, uselist, matchall, eapi, flat):
     raw = _c_dep_parser.parse(depstr, uselist=uselist, matchall=matchall)
     eapi_attrs = _get_eapi_attrs(eapi)
+    if flat:
+        return _c_flatten_result(raw, [], eapi, eapi_attrs, uselist, matchall)
     result = _c_convert_result(raw, eapi, eapi_attrs, uselist, matchall)
     return _c_normalize_seq(result, eapi_attrs.empty_groups_always_true)
 
@@ -1109,7 +1124,6 @@ def use_reduce(
         and token_class is Atom
         and not is_src_uri
         and not opconvert
-        and not flat
         and is_valid_flag is None
         and subset is None
         and not matchnone
@@ -1119,7 +1133,7 @@ def use_reduce(
         and (eapi is None or _get_eapi_attrs(eapi).slot_operator)
     ):
         try:
-            return _c_fast_use_reduce(depstr, uselist, matchall, eapi)
+            return _c_fast_use_reduce(depstr, uselist, matchall, eapi, flat)
         except ValueError as e:
             raise InvalidDependString(str(e)) from e
 

diff --git a/lib/portage/tests/dep/test_c_parser.py b/lib/portage/tests/dep/test_c_parser.py
index 765890452..3e91bd809 100644
--- a/lib/portage/tests/dep/test_c_parser.py
+++ b/lib/portage/tests/dep/test_c_parser.py
@@ -607,13 +607,19 @@ class TestCFastVsPython(TestCase):
         if _orig_c_dep_parser is None:
             self.skipTest("_parser extension not available")
 
-    def _compare(self, depstr, uselist=None, matchall=False, eapi="8"):
-        kw = dict(token_class=Atom, matchall=matchall, eapi=eapi, uselist=uselist or [])
+    def _compare(self, depstr, uselist=None, matchall=False, eapi="8", flat=False):
+        kw = dict(
+            token_class=Atom,
+            matchall=matchall,
+            eapi=eapi,
+            uselist=uselist or [],
+            flat=flat,
+        )
         with _use_c_parser(False):
             py_result = use_reduce(depstr, **kw)
         c_result = use_reduce(depstr, **kw)
         ok, msg = _result_equal(c_result, py_result)
-        self.assertTrue(ok, f"{depstr!r}: {msg}")
+        self.assertTrue(ok, f"{depstr!r} (flat={flat}): {msg}")
 
     def test_simple_atom(self):
         self._compare("dev-libs/foo")
@@ -758,6 +764,60 @@ class TestCFastVsPython(TestCase):
         self._compare("|| ( foo? ( dev-libs/a ) )", uselist=[], eapi="8")
         self._compare("|| ( foo? ( dev-libs/a ) )", uselist=[], eapi="6")
 
+    def test_flat_simple(self):
+        self._compare("dev-libs/a dev-libs/b", matchall=True, flat=True)
+
+    def test_flat_or_group(self):
+        self._compare("|| ( dev-libs/a dev-libs/b )", matchall=True, flat=True)
+
+    def test_flat_nested_or(self):
+        # Nested any-of groups keep a '||' token per level in flat mode.
+        self._compare(
+            "|| ( dev-libs/a || ( dev-libs/b dev-libs/c ) )",
+            matchall=True,
+            flat=True,
+        )
+
+    def test_flat_use_conditional_active(self):
+        self._compare("foo? ( dev-libs/a dev-libs/b )", uselist=["foo"], flat=True)
+
+    def test_flat_use_conditional_inactive(self):
+        self._compare("foo? ( dev-libs/a dev-libs/b )", uselist=[], flat=True)
+
+    def test_flat_nested_conditionals(self):
+        self._compare(
+            "a? ( dev-libs/a b? ( dev-libs/b ) ) c? ( dev-libs/c )",
+            uselist=["a", "c"],
+            flat=True,
+        )
+
+    def test_flat_conditional_or_mix(self):
+        self._compare(
+            "foo? ( || ( dev-libs/a dev-libs/b ) ) dev-libs/c",
+            uselist=["foo"],
+            flat=True,
+        )
+
+    def test_flat_atoms_with_use(self):
+        self._compare(
+            "dev-libs/a[x] foo? ( =dev-libs/b-1[y?] )", uselist=["foo"], flat=True
+        )
+
+    def test_flat_matchall(self):
+        self._compare(
+            "a? ( dev-libs/a ) !b? ( dev-libs/b ) || ( dev-libs/c dev-libs/d )",
+            matchall=True,
+            flat=True,
+        )
+
+    def test_flat_complex(self):
+        self._compare(
+            "dev-libs/A >=dev-libs/B-2.0 || ( dev-libs/C dev-libs/D ) "
+            "foo? ( =dev-libs/E-1.0:0[bar,baz?] || ( dev-libs/F dev-libs/G ) )",
+            uselist=["foo"],
+            flat=True,
+        )
+
     def test_invalid_raises_same_exception(self):
         bad_cases = [
             "|| ( dev-libs/a dev-libs/b",
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.