diff --git a/CHANGELOG.md b/CHANGELOG.md index d2f8d6c..496648f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Queries requiring and excluding the same condition now return no matches instead of raising `AssertionError`. - Component reads and tag cleanup no longer retain unused entities through empty registry rows. - Failed component removals no longer create empty rows, and clearing removes existing empty rows. - Replacing component relations from an inherited view now preserves values which depend on the destination. diff --git a/tcod/ecs/query.py b/tcod/ecs/query.py index 5c42921..6b16fcb 100644 --- a/tcod/ecs/query.py +++ b/tcod/ecs/query.py @@ -237,10 +237,6 @@ class _QueryLogicalAnd: _all_of: frozenset[_Query] = frozenset() _none_of: frozenset[_Query] = frozenset() - def __attrs_post_init__(self) -> None: - """Verify the current state.""" - assert self._all_of.isdisjoint(self._none_of) - def _add_to_cache(self, registry: Registry, cache: _QueryCache) -> None: for dependency in itertools.chain(self._all_of, self._none_of): cache.dependencies[dependency].add((registry, self)) diff --git a/tests/test_ecs.py b/tests/test_ecs.py index 4887fdd..006c2c2 100644 --- a/tests/test_ecs.py +++ b/tests/test_ecs.py @@ -7,7 +7,7 @@ import pickletools import sys from collections.abc import Callable, Iterator # noqa: TC003 -from typing import Final +from typing import Any, Final import pytest @@ -330,3 +330,29 @@ def test_type_form() -> None: def test_unpickle_is_a() -> None: assert pickle.loads(PICKLED_ISA) is tcod.ecs.IsA # noqa: S301 + + +@pytest.mark.parametrize("kind", ["component", "tag", "relation"]) +@pytest.mark.parametrize("reverse", [False, True]) +def test_query_contradictory_filters(kind: str, *, reverse: bool) -> None: + """A conjunction excluding its own required condition has no matches.""" + world = tcod.ecs.Registry() + actor = world["actor"] + conditions: dict[str, Any] + if kind == "component": + actor.components[int] = 3 + conditions = {"components": [int]} + elif kind == "tag": + actor.tags.add("actor") + conditions = {"tags": ["actor"]} + else: + actor.relation_tag["room"] = world["room"] + conditions = {"relations": [("room", world["room"])]} + included = world.Q.all_of(**conditions) + excluded = world.Q.none_of(**conditions) + query = excluded.all_of(**conditions) if reverse else included.none_of(**conditions) + assert not query + assert not query.get_entities() + actor.clear() + assert not query + assert not included