From 64f1319f0bb147d61cb080f07acd5f416d9c680e Mon Sep 17 00:00:00 2001 From: Arthur Zamarin Date: Fri, 7 Aug 2026 10:31:59 +0300 Subject: [PATCH] conditionals: finalize the empty DepSet default DepSet.__init__ defaulted restrictions to "", so every empty depset - what ebuild_src returns for a package lacking REQUIRED_USE, BDEPEND or IDEPEND - held a str rather than a tuple. boolean.base.__hash__ reads that as "not finalized" and raises TypeError, making those depsets permanently unhashable and reporting finalized=False in their repr. Nothing hashed them until 0.12.37 put an lru_cache on the compiled REQUIRED_USE constraints, keyed on the restriction. From then on find_constraint_satisfaction() blew up on any package without a REQUIRED_USE, which broke `pkgdev tatt` for most of the tree: File "pkgcore/restrictions/boolean.py", line 37, in __hash__ raise TypeError(f"{self!r} isn't finalized") TypeError: isn't finalized Default to an empty tuple instead. str(), len(), iteration and __eq__ (which compares set(restrictions)) are unchanged for the empty case, so the only difference is that the depset is now finalized, hence hashable. Reported-by: Eli Schwartz Signed-off-by: Arthur Zamarin --- NEWS.rst | 15 +++++++++++++++ src/pkgcore/ebuild/conditionals.py | 2 +- tests/ebuild/test_conditionals.py | 5 +++++ tests/restrictions/test_required_use.py | 9 +++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index cbbeb9450..6cdf28118 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -2,6 +2,21 @@ Release Notes ============= +---------------------------- +pkgcore 0.12.38 (unreleased) +---------------------------- + +Fixes +~~~~~ + +- ``pkgcore.ebuild.conditionals``: the empty depset returned for packages + lacking ``REQUIRED_USE``, ``BDEPEND`` or ``IDEPEND`` is now finalized, and + thus hashable again. Since caching the compiled REQUIRED_USE constraints in + 0.12.37 requires hashing the depset, this broke ``pkgdev tatt`` with a + ``TypeError`` for every package without ``REQUIRED_USE`` + (Arthur Zamarin) + + ---------------------------- pkgcore 0.12.37 (2026-07-31) ---------------------------- diff --git a/src/pkgcore/ebuild/conditionals.py b/src/pkgcore/ebuild/conditionals.py index 201fa9387..166a10866 100644 --- a/src/pkgcore/ebuild/conditionals.py +++ b/src/pkgcore/ebuild/conditionals.py @@ -30,7 +30,7 @@ class DepSet(boolean.AndRestriction, caching=False): def __init__( self, - restrictions="", + restrictions=(), element_class=atom, node_conds=True, known_conditionals=None, diff --git a/tests/ebuild/test_conditionals.py b/tests/ebuild/test_conditionals.py index f310f9c59..cc62fa9d5 100644 --- a/tests/ebuild/test_conditionals.py +++ b/tests/ebuild/test_conditionals.py @@ -220,6 +220,11 @@ def test_disabling_or(self): def test_atom_interaction(self): self.gen_depset("a/b[x(+)]", element_func=atom) + def test_empty_depset(self): + # an empty depset must be finalized, thus hashable + assert not conditionals.DepSet().restrictions + assert hash(conditionals.DepSet()) == hash(conditionals.DepSet()) + class TestDepSetConditionalsInspection(base): def test_sanity_has_conditionals(self): diff --git a/tests/restrictions/test_required_use.py b/tests/restrictions/test_required_use.py index 84168a21d..46d339402 100644 --- a/tests/restrictions/test_required_use.py +++ b/tests/restrictions/test_required_use.py @@ -19,6 +19,15 @@ def test_simple(): assert tuple(solver(required_use, {"bar", "foo"})) == ({"bar": True, "foo": True},) +def test_empty(): + # packages without REQUIRED_USE get an empty depset, which the constraint + # cache must still be able to key on + required_use = parse(required_use="") + solutions = tuple(solver(required_use, {"bar", "foo"})) + assert len(solutions) == 4 + assert {"bar": True, "foo": True} in solutions + + def test_negative_simple(): required_use = parse(required_use="!bar foo") assert tuple(solver(required_use, {"bar", "foo"})) == ({"bar": False, "foo": True},)