diff --git a/src/python/pants/engine/target.py b/src/python/pants/engine/target.py index 4128ceebe822..b61ee6eb172c 100644 --- a/src/python/pants/engine/target.py +++ b/src/python/pants/engine/target.py @@ -55,8 +55,10 @@ _DefaultBase = TypeVar("_DefaultBase", bound=Optional[ImmutableValue]) +# This @dataclass declaration is necessary for the `value` field to be propagated to the __hash__ of +# all subclasses, but MyPy doesn't recognize generic abstract dataclasses yet. +@dataclass(unsafe_hash=True) # type: ignore[misc] class Field(Generic[_DefaultBase], metaclass=ABCMeta): - # This is defined in PrimitiveField and AsyncField. value: _DefaultBase # Subclasses must define these. alias: ClassVar[str] @@ -65,12 +67,9 @@ class Field(Generic[_DefaultBase], metaclass=ABCMeta): required: ClassVar[bool] = False # This is a little weird to have an abstract __init__(). We do this to ensure that all - # subclasses have this exact type signature for their constructor. - # - # Normally, with dataclasses, each constructor parameter would instead be specified via a - # dataclass field declaration. But, we don't want to declare either `address` or `raw_value` as - # attributes because we make no assumptions whether the subclasses actually store those values - # on each instance. All that we care about is a common constructor interface. + # subclasses have this exact type signature for their constructor. Normally we would rely on the + # generated __init__ by declaring `address` as a field, but we don't want that on *every* Field + # subclass. @abstractmethod def __init__(self, raw_value: Optional[Any], *, address: Address) -> None: pass