def cast(obj, *, src_loc_at=0):
        """Cast :py:`obj` to a shape.

        Many :ref:`shape-like <lang-shapelike>` objects can be cast to a shape:

        * a :class:`Shape`, where the result is itself;
        * an :class:`int`, where the result is :func:`unsigned(obj) <unsigned>`;
        * a :class:`range`, where the result has minimal width required to represent all elements
          of the range, and is signed if any element of the range is signed;
        * an :class:`enum.Enum` whose members are all :ref:`constant-castable <lang-constcasting>`
          or :class:`enum.IntEnum`, where the result is wide enough to represent any member of
          the enumeration, and is signed if any member of the enumeration is signed;
        * a :class:`ShapeCastable` object, where the result is obtained by repeatedly calling
          :meth:`obj.as_shape() <ShapeCastable.as_shape>`.

        Raises
        ------
        TypeError
            If :py:`obj` cannot be converted to a :class:`Shape`.
        RecursionError
            If :py:`obj` is a :class:`ShapeCastable` object that casts to itself.
        """
        while True:
            if isinstance(obj, Shape):
                return obj
            elif isinstance(obj, ShapeCastable):
                new_obj = obj.as_shape()
            elif isinstance(obj, int):
                return Shape(obj)
            elif isinstance(obj, range):
                if len(obj) == 0:
                    return Shape(0)
                signed = obj[0] < 0 or obj[-1] < 0
                width  = max(bits_for(obj[0], signed),
                             bits_for(obj[-1], signed))
                if obj[0] == obj[-1] == 0:
                    width = 0
                return Shape(width, signed)
            elif isinstance(obj, type) and issubclass(obj, Enum):
                # For compatibility with third party enumerations, handle them as if they were
                # defined as subclasses of lib.enum.Enum with no explicitly specified shape.
                return Shape._cast_plain_enum(obj)
            else:
                raise TypeError(f"Object {obj!r} cannot be converted to an Amaranth shape")
            if new_obj is obj:
                raise RecursionError(f"Shape-castable object {obj!r} casts to itself")
            obj = new_obj
Cast :py:`obj` to a shape.

        Many :ref:`shape-like <lang-shapelike>` objects can be cast to a shape:

        * a :class:`Shape`, where the result is itself;
        * an :class:`int`, where the result is :func:`unsigned(obj) <unsigned>`;
        * a :class:`range`, where the result has minimal width required to represent all elements
          of the range, and is signed if any element of the range is signed;
        * an :class:`enum.Enum` whose members are all :ref:`constant-castable <lang-constcasting>`
          or :class:`enum.IntEnum`, where the result is wide enough to represent any member of
          the enumeration, and is signed if any member of the enumeration is signed;
        * a :class:`ShapeCastable` object, where the result is obtained by repeatedly calling
          :meth:`obj.as_shape() <ShapeCastable.as_shape>`.

        Raises
        ------
        TypeError
            If :py:`obj` cannot be converted to a :class:`Shape`.
        RecursionError
            If :py:`obj` is a :class:`ShapeCastable` object that casts to itself.
        
amaranth/hdl/_ast.py