- Start Date: 2023-12-04
- RFC PR: amaranth-lang/rfcs#35
- Amaranth Issue: amaranth-lang/amaranth#986
Add ShapeLike
, ValueLike
Summary
Two special classes are added to the language: ShapeLike
and ValueLike
. They cannot be constructed, but can be used to determine with isinstance
and issubclass
to determine whether something can be cast to Shape
or a Value
, respectively.
Motivation
As it stands, we have multiple types of objects that can be used as shapes (Shape
, ShapeCastable
, int
, range
, EnumMeta
) and values (Value
, ValueCastable
, int
, Enum
). These types have no common superclass, so there's no easy way to check if an object can be used as a shape or a value, save for actually calling Shape.cast
or Value.cast
. Introducing ShapeLike
and ValueLike
provides an idiomatic way to perform such a check.
Additionally, when type annotations are in use, there is currently no simple type that can be used for an argument that takes an arbitrary shape- or value-castable object. These new classes provide such a simple type.
Guide-level explanation
In Amaranth, multiple types of objects can be cast to shapes:
- actual
Shape
objects ShapeCastable
objects- non-negative integers
range
objectsEnum
subclasses with const-castable values
To check whether an object is of a type that can be cast to a shape, isinstance(obj, ShapeLike)
can be used. To check whether a type can be, in general, cast to a shape, issubclass(cls, ShapeLike)
can be used.
Likewise, multiple types of objects can be cast to values:
- actual
Value
objects ValueCastable
objects- integers
- values of
Enum
subclasses with const-castable values
To check whether an object is of a type that can be cast to a value, isinstance(obj, ValueLike)
can be used. To check whether a type can be, in general, cast to a value, issubclass(cls, ValueLike)
can be used.
Reference-level explanation
A ShapeLike
class is provided. It cannot be constructed, and can only be used with isinstance
and issubclass
, which are overriden by a custom metaclass.
issubclass(cls, ShapeLike)
returns True
for:
Shape
ShapeCastable
and its subclassesint
and its subclassesrange
and its subclassesenum.EnumMeta
and its subclasses
isinstance(obj, ShapeLike)
returns True
for:
- instances of
Shape
- instances of
ShapeCastable
and its subclasses - non-negative
int
values (andint
subclasses) enum.Enum
subclasses where every value is aValueLike
Similarly, a ValueLike
class is provided.
issubclass(cls, ValueLike)
returns True
for:
Value
and its subclassesValueCastable
and its subclassesint
and its subclassesenum.Enum
subclasses where every value is aValueLike
isinstance(obj, ValueLike)
returns True
iff issubclass(type(obj), ValueLike)
returns True
.
Drawbacks
More moving parts in the language.
isinstance(obj, ShapeLike)
does not actually guarantee that Shape.cast(obj)
will succeed — the instance check looks only at surface-level information, and an exception can still be thrown. issubclass(cls, ShapeLike)
is, by necessity, even more inaccurate.
Rationale and alternatives
There are many ways to implement the instance and subclass checks, some more precise (and complex) than others. The semantics described above are a compromise.
For isinstance
, a simple variant would be to just try Shape.cast
or Value.cast
and see if it raises an exception. However, this will sometimes result in isinstance(MyShapeCastable(), ShapeLike)
returning False
, which may be very unintuitive and hide bugs.
The check for a valid shape-castable enum described above is an approximation — the actual logic used requires all values of an enum to be const-castable, not just value-castable. However, there is no way to check this without actually invoking Value.cast
on the enum members.
Prior art
Python has the concept of abstract base classes, such as collections.abc.Sequence
, which can be used for subclass checking even if they are not actual superclasses of the types involved. ShapeLike
and ValueLike
are effectively ABCs, though they do not use the actual ABC machinery (due to having custom logic in instance checking).
Unresolved questions
- Should the exact details of the instance and subclass checks be changed?
Future possibilities
A similar ABC-like class has been proposed for lib.wiring
interfaces.