Files
Giacomo Vercesi cd3cd5c4d1 pypeline: fix Request.check
The function `Request.check` did not work properly due to
`Container.contains_all` performing the check with the operands swapped.
Fix the call and improve the body of `ObjectSet.issubset`.
Also improve the error message, highlighting the missing objects.
2025-12-10 15:05:53 +01:00

129 lines
4.7 KiB
Python

#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
from __future__ import annotations
from typing import Dict, MutableMapping, Optional, Set, TypeVar, cast, overload
from revng.pypeline.container import ContainerDeclaration, ContainerSet
from revng.pypeline.object import ObjectSet
_DUMMY = object()
T = TypeVar("T")
class Requests(MutableMapping[ContainerDeclaration, ObjectSet]):
"""
A map from a container declaration to the objects that we'd like to see there (an ObjectList).
"""
def __init__(self, requests: Optional[Dict[ContainerDeclaration, ObjectSet]] = None):
self.requests: Dict[ContainerDeclaration, ObjectSet] = {}
if requests:
self.requests.update(requests)
def clone(self) -> Requests:
result = Requests()
result.requests = {k: v.clone() for k, v in self.requests.items()}
return result
def _requests_for(self, declaration: ContainerDeclaration) -> ObjectSet:
"""
If the key is not present, we create a new ObjectSet for it.
"""
kind = declaration.container_type.kind
if declaration in self.requests:
assert self.requests[declaration].kind == kind
else:
self.requests[declaration] = ObjectSet(kind=kind)
return self.requests[declaration]
def merge(self, other: Requests) -> None:
for container, objects in other.items():
self._requests_for(container).update(objects)
def extract(self, requested_containers: Set[ContainerDeclaration]) -> Requests:
results = Requests()
for container, objects in list(self.requests.items()):
if container in requested_containers:
assert container in self.requests
results.requests[container] = objects
del self.requests[container]
return results
def check(self, containers: ContainerSet):
"""
Check if the given containers set satisfies these requests.
"""
# NOTE: this can be done either here or in the ContainerSet class, but
# currently it's a dict, so it's easier to do it here
for decl, objects in self.requests.items():
if decl not in containers:
raise ValueError(f"Container {decl} is not present in the given ContainerSet.")
if not containers[decl].contains_all(objects):
diff = objects - containers[decl].objects()
raise ValueError(
f"Container {containers[decl]} of declaration {decl} does "
f"not contain all requested objects, missing objects: {diff}"
)
def empty(self) -> bool:
return sum(map(len, self.requests.values())) == 0
def __iter__(self):
return self.requests.__iter__()
def __getitem__(self, key: ContainerDeclaration) -> ObjectSet:
return self.requests[key]
@overload
def get(self, key: ContainerDeclaration, /) -> ObjectSet: ...
@overload
def get(self, key: ContainerDeclaration, default: T, /) -> ObjectSet | T: ...
def get(self, key: ContainerDeclaration, default: T | object = _DUMMY, /) -> ObjectSet | T:
if key in self.requests:
return self.requests[key]
elif default is _DUMMY:
return ObjectSet(key.container_type.kind)
else:
return cast(T, default)
def __setitem__(self, key: ContainerDeclaration, value: ObjectSet) -> None:
if not isinstance(value, ObjectSet):
raise TypeError(f"Expected ObjectSet, got {type(value)}")
self.requests[key] = value
def __delitem__(self, key: ContainerDeclaration) -> None:
if key in self.requests:
del self.requests[key]
else:
raise KeyError(f"Container {key} not found in requests.")
def __len__(self) -> int:
return len(self.requests)
def insert(self, container: ContainerDeclaration, object_list: ObjectSet) -> None:
"""
Insert a new request for the given container and object list.
This is similar to update, but it does not check if the container already exists.
"""
if not isinstance(object_list, ObjectSet):
raise TypeError(f"Expected ObjectSet, got {type(object_list)}")
self._requests_for(container).update(object_list)
def __repr__(self):
return repr(dict(self.requests))
def __eq__(self, other) -> bool:
return self.requests == other.requests
def minimize(self) -> Requests:
"""Return the same request object, but with all the empty values removed"""
return Requests({k: v for k, v in self.requests.items() if len(v) > 0})