Implement RFC 35: Add ShapeLike, ValueLike.

This commit is contained in:
Wanda 2023-12-03 04:03:13 +01:00 committed by Catherine
parent 422ba9ea51
commit e9545efb22
7 changed files with 192 additions and 30 deletions

View file

@ -4,7 +4,7 @@ import warnings
import functools
from collections import OrderedDict
from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequence
from enum import Enum
from enum import Enum, EnumMeta
from itertools import chain
from ._repr import *
@ -15,11 +15,11 @@ from .._unused import *
__all__ = [
"Shape", "signed", "unsigned", "ShapeCastable",
"Shape", "signed", "unsigned", "ShapeCastable", "ShapeLike",
"Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
"Array", "ArrayProxy",
"Signal", "ClockSignal", "ResetSignal",
"ValueCastable",
"ValueCastable", "ValueLike",
"Sample", "Past", "Stable", "Rose", "Fell", "Initial",
"Statement", "Switch",
"Property", "Assign", "Assert", "Assume", "Cover",
@ -150,6 +150,52 @@ class Shape:
self.width == other.width and self.signed == other.signed)
class _ShapeLikeMeta(type):
def __subclasscheck__(cls, subclass):
return issubclass(subclass, (Shape, ShapeCastable, int, range, EnumMeta)) or subclass is ShapeLike
def __instancecheck__(cls, instance):
if isinstance(instance, (Shape, ShapeCastable, range)):
return True
if isinstance(instance, int):
return instance >= 0
if isinstance(instance, EnumMeta):
for member in instance:
if not isinstance(member.value, ValueLike):
return False
return True
return False
@final
class ShapeLike(metaclass=_ShapeLikeMeta):
"""An abstract class representing all objects that can be cast to a :class:`Shape`.
``issubclass(cls, ShapeLike)`` returns ``True`` for:
- :class:`Shape`
- :class:`ShapeCastable` and its subclasses
- ``int`` and its subclasses
- ``range`` and its subclasses
- :class:`enum.EnumMeta` and its subclasses
- :class:`ShapeLike` itself
``isinstance(obj, ShapeLike)`` returns ``True`` for:
- :class:`Shape` instances
- :class:`ShapeCastable` instances
- non-negative ``int`` values
- ``range`` instances
- :class:`enum.Enum` subclasses where all values are :ref:`value-like <lang-valuelike>`
This class is only usable for the above checks no instances and no (non-virtual)
subclasses can be created.
"""
def __new__(cls, *args, **kwargs):
raise TypeError("ShapeLike is an abstract class and cannot be constructed")
def unsigned(width):
"""Shorthand for ``Shape(width, signed=False)``."""
return Shape(width, signed=False)
@ -1479,6 +1525,40 @@ class ValueCastable:
return wrapper_memoized
class _ValueLikeMeta(type):
"""An abstract class representing all objects that can be cast to a :class:`Value`.
``issubclass(cls, ValueLike)`` returns ``True`` for:
- :class:`Value`
- :class:`ValueCastable` and its subclasses
- ``int`` and its subclasses
- :class:`enum.Enum` subclasses where all values are :ref:`value-like <lang-valuelike>`
- :class:`ValueLike` itself
``isinstance(obj, ValueLike)`` returns the same value as ``issubclass(type(obj), ValueLike)``.
This class is only usable for the above checks no instances and no (non-virtual)
subclasses can be created.
"""
def __subclasscheck__(cls, subclass):
if issubclass(subclass, (Value, ValueCastable, int)) or subclass is ValueLike:
return True
if issubclass(subclass, Enum):
return isinstance(subclass, ShapeLike)
return False
def __instancecheck__(cls, instance):
return issubclass(type(instance), cls)
@final
class ValueLike(metaclass=_ValueLikeMeta):
def __new__(cls, *args, **kwargs):
raise TypeError("ValueLike is an abstract class and cannot be constructed")
# TODO(amaranth-0.5): remove
@final
class Sample(Value):

View file

@ -26,7 +26,7 @@ class Field:
Attributes
----------
shape : :ref:`shape-castable <lang-shapecasting>`
shape : :ref:`shape-like <lang-shapelike>`
Shape of the field. When initialized or assigned, the object is stored as-is.
offset : :class:`int`, >=0
Index of the least significant bit of the field.
@ -56,7 +56,7 @@ class Field:
"""Width of the field.
This property should be used over ``self.shape.width`` because ``self.shape`` can be
an arbitrary :ref:`shape-castable <lang-shapecasting>` object, which may not have
an arbitrary :ref:`shape-like <lang-shapelike>` object, which may not have
a ``width`` property.
Returns
@ -82,7 +82,7 @@ class Field:
class Layout(ShapeCastable, metaclass=ABCMeta):
"""Description of a data layout.
The :ref:`shape-castable <lang-shapecasting>` :class:`Layout` interface associates keys
The :ref:`shape-like <lang-shapelike>` :class:`Layout` interface associates keys
(string names or integer indexes) with fields, giving identifiers to spans of bits in
an Amaranth value.
@ -96,7 +96,7 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
@staticmethod
def cast(obj):
"""Cast a :ref:`shape-castable <lang-shapecasting>` object to a layout.
"""Cast a :ref:`shape-like <lang-shapelike>` object to a layout.
This method performs a subset of the operations done by :meth:`Shape.cast`; it will
recursively call ``.as_shape()``, but only until a layout is returned.
@ -279,7 +279,7 @@ class StructLayout(Layout):
Attributes
----------
members : mapping of :class:`str` to :ref:`shape-castable <lang-shapecasting>`
members : mapping of :class:`str` to :ref:`shape-like <lang-shapelike>`
Dictionary of structure members.
"""
@ -350,7 +350,7 @@ class UnionLayout(Layout):
Attributes
----------
members : mapping of :class:`str` to :ref:`shape-castable <lang-shapecasting>`
members : mapping of :class:`str` to :ref:`shape-like <lang-shapelike>`
Dictionary of union members.
"""
def __init__(self, members):
@ -425,7 +425,7 @@ class ArrayLayout(Layout):
Attributes
----------
elem_shape : :ref:`shape-castable <lang-shapecasting>`
elem_shape : :ref:`shape-like <lang-shapelike>`
Shape of an individual element.
length : :class:`int`
Amount of elements.
@ -567,7 +567,7 @@ class FlexibleLayout(Layout):
class View(ValueCastable):
"""A value viewed through the lens of a layout.
The :ref:`value-castable <lang-valuecasting>` class :class:`View` provides access to the fields
The :ref:`value-like <lang-valuelike>` class :class:`View` provides access to the fields
of an underlying Amaranth value via the names or indexes defined in the provided layout.
Creating a view
@ -583,7 +583,7 @@ class View(ValueCastable):
a value-castable object. If the shape of the field is a :class:`Layout`, it will be
a :class:`View`; if it is a class deriving from :class:`Struct` or :class:`Union`, it
will be an instance of that data class; if it is another
:ref:`shape-castable <lang-shapecasting>` object implementing ``__call__``, it will be
:ref:`shape-like <lang-shapelike>` object implementing ``__call__``, it will be
the result of calling that method.
Slicing a view whose layout is an :class:`ArrayLayout` can be done with an index that is
@ -859,7 +859,7 @@ class Struct(View, metaclass=_AggregateMeta):
to describe the structure layout and reset values for the fields using Python
:term:`variable annotations <python:variable annotation>`.
Any annotations containing :ref:`shape-castable <lang-shapecasting>` objects are used,
Any annotations containing :ref:`shape-like <lang-shapelike>` objects are used,
in the order in which they appear in the source code, to construct a :class:`StructLayout`.
The values assigned to such annotations are used to populate the reset value of the signal
created by the view. Any other annotations are kept as-is.

View file

@ -19,13 +19,13 @@ class EnumMeta(ShapeCastable, py_enum.EnumMeta):
protocol.
This metaclass provides the :meth:`as_shape` method, making its instances
:ref:`shape-castable <lang-shapecasting>`, and accepts a ``shape=`` keyword argument
:ref:`shape-like <lang-shapelike>`, and accepts a ``shape=`` keyword argument
to specify a shape explicitly. Other than this, it acts the same as the standard
:class:`enum.EnumMeta` class; if the ``shape=`` argument is not specified and
:meth:`as_shape` is never called, it places no restrictions on the enumeration class
or the values of its members.
When a :ref:`value-castable <lang-valuecasting>` is cast to an enum type that is an instance
When a :ref:`value-like <lang-valuelike>` is cast to an enum type that is an instance
of this metaclass, it can be automatically wrapped in a view class. A custom view class
can be specified by passing the ``view_class=`` keyword argument when creating the enum class.
"""
@ -139,7 +139,7 @@ class EnumMeta(ShapeCastable, py_enum.EnumMeta):
When given an integer constant, it returns the corresponding enum value, like a standard
Python enumeration.
When given a :ref:`value-castable <lang-valuecasting>`, it is cast to a value, then wrapped
When given a :ref:`value-like <lang-valuelike>`, it is cast to a value, then wrapped
in the ``view_class`` specified for this enum type (:class:`EnumView` for :class:`Enum`,
:class:`FlagView` for :class:`Flag`, or a custom user-defined class). If the type has no
``view_class`` (like :class:`IntEnum` or :class:`IntFlag`), a plain
@ -214,7 +214,7 @@ class EnumView(ValueCastable):
def __init__(self, enum, target):
"""Constructs a view with the given enum type and target
(a :ref:`value-castable <lang-valuecasting>`).
(a :ref:`value-like <lang-valuelike>`).
"""
if not isinstance(enum, EnumMeta) or not hasattr(enum, "_amaranth_shape_"):
raise TypeError(f"EnumView type must be an enum with shape, not {enum!r}")
@ -312,7 +312,7 @@ class FlagView(EnumView):
values of the same enum type."""
def __invert__(self):
"""Inverts all flags in this value and returns another :ref:`FlagView`.
"""Inverts all flags in this value and returns another :class:`FlagView`.
Note that this is not equivalent to applying bitwise negation to the underlying value:
just like the Python :class:`enum.Flag` class, only bits corresponding to flags actually