Repository: amaranth-lang/amaranth
Pull request #1058 (2024-01-30 02:01:22)

Start writing reference documentation for the language
[Rendered](https://whitequark.github.io/amaranth/docs/reference/reference.html)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1058?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) Report
Attention: `6 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`65d77f0`)](https://app.codecov.io/gh/amaranth-lang/amaranth/commit/65d77f03fe7a75182b4263cd11cc1251b393dad7?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) 86.52% compared to head [(`25bbb26`)](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1058?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) 86.47%.
> Report is 2 commits behind head on main.

| [Files](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1058?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) | Patch % | Lines |
|---|---|---|
| [amaranth/hdl/ast.py](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1058?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang#diff-YW1hcmFudGgvaGRsL2FzdC5weQ==) | 71.42% | [4 Missing and 2 partials :warning: ](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1058?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1058      +/-   ##
==========================================
- Coverage   86.52%   86.47%   -0.05%     
==========================================
  Files          41       41              
  Lines        7153     7157       +4     
  Branches     1709     1709              
==========================================
  Hits         6189     6189              
- Misses        796      801       +5     
+ Partials      168      167       -1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1058?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).


Patch:
diff --git a/amaranth/__init__.py b/amaranth/__init__.py
index 95a6cce..aef4134 100644
--- a/amaranth/__init__.py
+++ b/amaranth/__init__.py
@@ -12,7 +12,7 @@ del importlib
 
 from .hdl import *
 
-
+# must be kept in sync with docs/reference.rst!
 __all__ = [
     "Shape", "unsigned", "signed",
     "Value", "Const", "C", "Mux", "Cat", "Repl", "Array", "Signal", "ClockSignal", "ResetSignal",
diff --git a/amaranth/hdl/_ast.py b/amaranth/hdl/_ast.py
index 9d4bb27..b86bf44 100644
--- a/amaranth/hdl/_ast.py
+++ b/amaranth/hdl/_ast.py
@@ -1,5 +1,4 @@
 from abc import ABCMeta, abstractmethod
-import inspect
 import warnings
 import functools
 import operator
@@ -12,7 +11,6 @@ from ._repr import *
 from .. import tracer
 from ..utils import *
 from .._utils import *
-from .._utils import _ignore_deprecated
 from .._unused import *
 
 
@@ -37,51 +35,23 @@ class DUID:
         DUID.__next_uid += 1
 
 
-class ShapeCastable:
-    """Interface of user-defined objects that can be cast to :class:`Shape` s.
-
-    An object deriving from :class:`ShapeCastable` is automatically converted to a :class:`Shape`
-    when it is used in a context where a :class:`Shape` is expected. Such objects can contain
-    a richer description of the shape than what is supported by the core Amaranth language, yet
-    still be transparently used with it.
-    """
-    def __init_subclass__(cls, **kwargs):
-        if not hasattr(cls, "as_shape"):
-            raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
-                            f"the `as_shape` method")
-        if not (hasattr(cls, "__call__") and inspect.isfunction(cls.__call__)):
-            raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
-                            f"the `__call__` method")
-        if not hasattr(cls, "const"):
-            raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
-                            f"the `const` method")
-
-    def _value_repr(self, value):
-        return (Repr(FormatInt(), value),)
-
-
 class Shape:
     """Bit width and signedness of a value.
 
-    A ``Shape`` can be constructed using:
-      * explicit bit width and signedness;
-      * aliases :func:`signed` and :func:`unsigned`;
-      * casting from a variety of objects.
+    A :class:`Shape` can be obtained by:
 
-    A ``Shape`` can be cast from:
-      * an integer, where the integer specifies the bit width;
-      * a range, where the result is wide enough to represent any element of the range, and is
-        signed if any element of the range is signed;
-      * an :class:`Enum` with all integer members or :class:`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.
+    * constructing with explicit bit width and signedness;
+    * using the :func:`signed` and :func:`unsigned` aliases if the signedness is known upfront;
+    * casting from a variety of objects using the :meth:`cast` method.
 
     Parameters
     ----------
     width : int
-        The number of bits in the representation, including the sign bit (if any).
+        The number of bits in the representation of a value. This includes the sign bit for signed
+        values. Cannot be zero if the value is signed.
     signed : bool
-        If ``False``, the value is unsigned. If ``True``, the value is signed two's complement.
+        Whether the value is signed. Signed values use the
+        `two's complement <https://en.wikipedia.org/wiki/Two's_complement>`_ representation.
     """
     def __init__(self, width=1, signed=False):
         if not isinstance(width, int):
@@ -117,6 +87,27 @@ class Shape:
 
     @staticmethod
     def cast(obj, *, src_loc_at=0):
+        """Cast :pc:`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 is wide enough to represent any element 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 :pc:`obj` cannot be converted to a :class:`Shape`.
+        RecursionError
+            If :pc:`obj` is a :class:`ShapeCastable` object that casts to itself.
+        """
         while True:
             if isinstance(obj, Shape):
                 return obj
@@ -142,6 +133,10 @@ class Shape:
             obj = new_obj
 
     def __repr__(self):
+        """Python code that creates this shape.
+
+        Returns :pc:`f"signed({self.width})"` or :pc:`f"unsigned({self.width})"`.
+        """
         if self.signed:
             return f"signed({self.width})"
         else:
@@ -152,6 +147,158 @@ class Shape:
                 self.width == other.width and self.signed == other.signed)
 
 
+def unsigned(width):
+    """Returns :pc:`Shape(width, signed=False)`."""
+    return Shape(width, signed=False)
+
+
+def signed(width):
+    """Returns :pc:`Shape(width, signed=True)`."""
+    return Shape(width, signed=True)
+
+
+class ShapeCastable:
+    """Interface class for objects that can be cast to a :class:`Shape`.
+
+    Shapes of values in the Amaranth language are specified using :ref:`shape-like objects
+    <lang-shapelike>`. Inheriting a class from :class:`ShapeCastable` and implementing all of
+    the methods described below adds instances of that class to the list of shape-like objects
+    recognized by the :meth:`Shape.cast` method. This is a part of the mechanism for seamlessly
+    extending the Amaranth language in third-party code.
+
+    To illustrate their purpose, consider constructing a signal from a shape-castable object
+    :pc:`shape_castable`:
+
+    .. code::
+
+        value_like = Signal(shape_castable, reset=initializer)
+
+    The code above is equivalent to:
+
+    .. code::
+
+        value_like = shape_castable(Signal(
+            shape_castable.as_shape(),
+            reset=shape_castable.const(initializer)
+        ))
+
+    Note that the :pc:`shape_castable(x)` syntax performs :pc:`shape_castable.__call__(x)`.
+
+    .. tip::
+
+        The source code of the :mod:`amaranth.lib.data` module can be used as a reference for
+        implementing a fully featured shape-castable object.
+    """
+    def __init_subclass__(cls, **kwargs):
+        if cls.as_shape is ShapeCastable.as_shape:
+            raise TypeError(f"Class '{cls.__name__}' deriving from 'ShapeCastable' must override "
+                            f"the 'as_shape' method")
+        if cls.const is ShapeCastable.const:
+            raise TypeError(f"Class '{cls.__name__}' deriving from 'ShapeCastable' must override "
+                            f"the 'const' method")
+        if cls.__call__ is ShapeCastable.__call__:
+            raise TypeError(f"Class '{cls.__name__}' deriving from 'ShapeCastable' must override "
+                            f"the '__call__' method")
+
+    # The signatures and definitions of these methods are weird because they are present here for
+    # documentation (and error checking above) purpose only and should not affect control flow.
+    # This especially applies to `__call__`, where subclasses may call `super().__call__()` in
+    # creative ways.
+
+    def as_shape(self, *args, **kwargs):
+        """as_shape()
+
+        Convert :pc:`self` to a :ref:`shape-like object <lang-shapelike>`.
+
+        This method is called by the Amaranth language to convert :pc:`self` to a concrete
+        :class:`Shape`. It will usually return a :class:`Shape` object, but it may also return
+        another shape-like object to delegate its functionality.
+
+        This method must be idempotent: when called twice on the same object, the result must be
+        exactly the same.
+
+        This method may also be called by code that is not a part of the Amaranth language.
+
+        Returns
+        -------
+        Any other object recognized by :meth:`Shape.cast`.
+
+        Raises
+        ------
+        Exception
+            When the conversion cannot be done. This exception must be propagated by callers
+            (except when checking whether an object is shape-castable or not), either directly
+            or as a cause of another exception.
+        """
+        return super().as_shape(*args, **kwargs) # :nocov:
+
+    def const(self, *args, **kwargs):
+        """const(obj)
+
+        Convert a constant initializer :pc:`obj` to its value representation.
+
+        This method is called by the Amaranth language to convert :pc:`obj`, which may be an
+        arbitrary Python object, to a concrete :ref:`value-like object <lang-valuelike>`.
+        The object :pc:`obj` will usually be a Python literal that can conveniently represent
+        a constant value whose shape is described by :pc:`self`. While not constrained here,
+        the result will usually be an instance of the return type of :meth:`__call__`.
+
+        For any :pc:`obj`, the following condition must hold:
+
+        .. code::
+
+            Shape.cast(self) == Const.cast(self.const(obj)).shape()
+
+        This method may also be called by code that is not a part of the Amaranth language.
+
+        Returns
+        -------
+        A :ref:`value-like object <lang-valuelike>` that is :ref:`constant-castable <lang-constcasting>`.
+
+        Raises
+        ------
+        Exception
+            When the conversion cannot be done. This exception must be propagated by callers,
+            either directly or as a cause of another exception. While not constrained here,
+            usually the exception class will be :exc:`TypeError` or :exc:`ValueError`.
+        """
+        return super().const(*args, **kwargs) # :nocov:
+
+    def __call__(self, *args, **kwargs):
+        """__call__(obj)
+
+        Lift a :ref:`value-like object <lang-valuelike>` to a higher-level representation.
+
+        This method is called by the Amaranth language to lift :pc:`obj`, which may be any
+        :ref:`value-like object <lang-valuelike>` whose shape equals :pc:`Shape.cast(self)`,
+        to a higher-level representation, which may be any value-like object with the same
+        shape. While not constrained here, usually a :class:`ShapeCastable` implementation will
+        be paired with a :class:`ValueCastable` implementation, and this method will return
+        an instance of the latter.
+
+        If :pc:`obj` is not as described above, this interface does not constrain the behavior
+        of this method. This may be used to implement another call-based protocol at the same
+        time.
+
+        For any compliant :pc:`obj`, the following condition must hold:
+
+        .. code::
+
+            Value.cast(self(obj)) == Value.cast(obj)
+
+        This method may also be called by code that is not a part of the Amaranth language.
+
+        Returns
+        -------
+        A :ref:`value-like object <lang-valuelike>`.
+        """
+        return super().__call__(*args, **kwargs) # :nocov:
+
+    # TODO: write an RFC for turning this into a proper interface method
+    def _value_repr(self, value):
+        return (Repr(FormatInt(), value),)
+
+
 class _ShapeLikeMeta(type):
     def __subclasscheck__(cls, subclass):
         return issubclass(subclass, (Shape, ShapeCastable, int, range, EnumMeta)) or subclass is ShapeLike
@@ -173,39 +320,28 @@ class _ShapeLikeMeta(type):
 class ShapeLike(metaclass=_ShapeLikeMeta):
     """An abstract class representing all objects that can be cast to a :class:`Shape`.
 
-    ``issubclass(cls, ShapeLike)`` returns ``True`` for:
+    :pc:`issubclass(cls, ShapeLike)` returns :pc:`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
+    * :class:`Shape`;
+    * :class:`ShapeCastable` and its subclasses;
+    * :class:`int` and its subclasses;
+    * :class:`range` and its subclasses;
+    * :class:`enum.EnumMeta` and its subclasses;
+    * :class:`ShapeLike` itself.
 
-    ``isinstance(obj, ShapeLike)`` returns ``True`` for:
+    :pc:`isinstance(obj, ShapeLike)` returns :pc:`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>`
+    * :class:`Shape` instances;
+    * :class:`ShapeCastable` instances;
+    * non-negative :class:`int` values;
+    * :class:`range` instances;
+    * :class:`enum.Enum` subclasses where all values are :ref:`value-like objects <lang-valuelike>`.
 
-    This class is only usable for the above checks — no instances and no (non-virtual)
-    subclasses can be created.
+    This class cannot be instantiated or subclassed. It can only be used for checking types of
+    objects.
     """
-
     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)
-
-
-def signed(width):
-    """Shorthand for ``Shape(width, signed=True)``."""
-    return Shape(width, signed=True)
+        raise TypeError("ShapeLike is an abstract class and cannot be instantiated")
 
 
 def _overridable_by_reflected(method_name):
diff --git a/docs/lang.rst b/docs/guide.rst
similarity index 97%
rename from docs/lang.rst
rename to docs/guide.rst
index 81b9e57..ccd611d 100644
--- a/docs/lang.rst
+++ b/docs/guide.rst
@@ -6,11 +6,13 @@
 Language guide
 ##############
 
+.. py:currentmodule:: amaranth.hdl
+
 .. warning::
 
    This guide is a work in progress and is seriously incomplete!
 
-This guide introduces the Amaranth language in depth. It assumes familiarity with synchronous digital logic and the Python programming language, but does not require prior experience with any hardware description language. See the :doc:`tutorial <tutorial>` for a step-by-step introduction to the language.
+This guide introduces the Amaranth language in depth. It assumes familiarity with synchronous digital logic and the Python programming language, but does not require prior experience with any hardware description language. See the :doc:`tutorial <tutorial>` for a step-by-step introduction to the language, and the :doc:`reference <reference>` for a detailed description of the Python classes that underlie the language's syntax.
 
 .. TODO: link to a good synchronous logic tutorial and a Python tutorial?
 
@@ -44,7 +46,7 @@ All of the examples below assume that a glob import is used.
 Shapes
 ======
 
-A ``Shape`` is an object with two attributes, ``.width`` and ``.signed``. It can be constructed directly:
+A :class:`Shape` describes the bit width and signedness of an Amaranth value. It can be constructed directly:
 
 .. doctest::
 
@@ -53,7 +55,7 @@ A ``Shape`` is an object with two attributes, ``.width`` and ``.signed``. It can
    >>> Shape(width=12, signed=True)
    signed(12)
 
-However, in most cases, the shape is always constructed with the same signedness, and the aliases ``signed`` and ``unsigned`` are more convenient:
+However, in most cases, the signedness of a shape is known upfront, and the convenient aliases :func:`signed` and :func:`unsigned` can be used:
 
 .. doctest::
 
@@ -126,9 +128,9 @@ The shape of the constant can be specified explicitly, in which case the number'
 Shape casting
 =============
 
-Shapes can be *cast* from other objects, which are called *shape-like*. Casting is a convenient way to specify a shape indirectly, for example, by a range of numbers representable by values with that shape.
+Shapes can be *cast* from other objects, which are called *shape-like*. Casting is a convenient way to specify a shape indirectly, for example, by a range of numbers representable by values with that shape. Shapes are shape-like objects as well.
 
-Casting to a shape can be done explicitly with ``Shape.cast``, but is usually implicit, since shape-like objects are accepted anywhere shapes are.
+Casting to a shape can be done explicitly with :meth:`Shape.cast`, but is usually implicit, since shape-like objects are accepted anywhere shapes are.
 
 
 .. _lang-shapeint:
@@ -136,7 +138,7 @@ Casting to a shape can be done explicitly with ``Shape.cast``, but is usually im
 Shapes from integers
 --------------------
 
-Casting a shape from an integer ``i`` is a shorthand for constructing a shape with ``unsigned(i)``:
+Casting a shape from an integer ``i`` is a shorthand for constructing a shape with :func:`unsigned(i) <unsigned>`:
 
 .. doctest::
 
@@ -186,7 +188,7 @@ Specifying a shape with a range is convenient for counters, indexes, and all oth
 
 .. note::
 
-   An empty range always casts to an ``unsigned(0)``, even if both of its bounds are negative.
+   An empty range always casts to an :py:`unsigned(0)`, even if both of its bounds are negative.
    This happens because, being empty, it does not contain any negative values.
 
    .. doctest::
@@ -244,22 +246,30 @@ The :mod:`amaranth.lib.enum` module extends the standard enumerations such that
    The enumeration does not have to subclass :class:`enum.IntEnum` or have :class:`int` as one of its base classes; it only needs to have integers as values of every member. Using enumerations based on :class:`enum.Enum` rather than :class:`enum.IntEnum` prevents unwanted implicit conversion of enum members to integers.
 
 
+.. _lang-shapecustom:
+
+Custom shapes
+-------------
+
+Any Python value that implements the :class:`ShapeCastable` interface can extend the language with a custom shape-like object. For example, the standard library module :mod:`amaranth.lib.data` uses this facility to add support for aggregate data types to the language.
+
+
 .. _lang-valuelike:
 
 Value casting
 =============
 
-Like shapes, values may be *cast* from other objects, which are called *value-like*. Casting to values allows objects that are not provided by Amaranth, such as integers or enumeration members, to be used in Amaranth expressions directly.
+Like shapes, values may be *cast* from other objects, which are called *value-like*. Casting to values allows objects that are not provided by Amaranth, such as integers or enumeration members, to be used in Amaranth expressions directly. Values are value-like objects as well.
 
 .. TODO: link to ValueCastable
 
-Casting to a value can be done explicitly with ``Value.cast``, but is usually implicit, since value-like objects are accepted anywhere values are.
+Casting to a value can be done explicitly with :meth:`Value.cast`, but is usually implicit, since value-like objects are accepted anywhere values are.
 
 
 Values from integers
 --------------------
 
-Casting a value from an integer ``i`` is equivalent to ``Const(i)``:
+Casting a value from an integer ``i`` is equivalent to :class:`Const(i) <Const>`:
 
 .. doctest::
 
@@ -344,7 +354,7 @@ A *signal* is a value representing a (potentially) varying number. Signals can b
 Signal shapes
 -------------
 
-A signal can be created with an explicitly specified shape (any :ref:`shape-like <lang-shapelike>` object); if omitted, the shape defaults to ``unsigned(1)``. Although rarely useful, 0-bit signals are permitted.
+A signal can be created with an explicitly specified shape (any :ref:`shape-like <lang-shapelike>` object); if omitted, the shape defaults to :func:`unsigned(1) <unsigned>`. Although rarely useful, 0-bit signals are permitted.
 
 .. doctest::
 
diff --git a/docs/index.rst b/docs/index.rst
index 0e6e0df..0228708 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -12,7 +12,8 @@ Language & toolchain
    install
    start
    tutorial
-   lang
+   guide
+   reference
    stdlib
    platform
    changes
diff --git a/docs/intro.rst b/docs/intro.rst
index 2fed044..33ec2f2 100644
--- a/docs/intro.rst
+++ b/docs/intro.rst
@@ -16,7 +16,7 @@ The Amaranth toolchain consists of the :ref:`Amaranth language <intro-lang>`, th
 The Amaranth language
 =====================
 
-The :doc:`Amaranth hardware description language <lang>` is a Python library for register transfer level modeling of synchronous logic. Ordinary Python code is used to construct a netlist of a digital circuit, which can be simulated, directly synthesized via Yosys_, or converted to human-readable Verilog code for use with industry-standard toolchains.
+The :doc:`Amaranth hardware description language <guide>` is a Python library for register transfer level modeling of synchronous logic. Ordinary Python code is used to construct a netlist of a digital circuit, which can be simulated, directly synthesized via Yosys_, or converted to human-readable Verilog code for use with industry-standard toolchains.
 
 By relying on the flexibility, rich functionality and widespread adoption of the Python language, the Amaranth language is focused on a single task: modeling digital logic well. It has first-class support for building blocks like clock domains and finite state machines, and uses simple rules for arithmetic operations that closely match the Python semantics. Python classes, functions, loops and conditionals can be used to build organized and flexible designs; Python libraries can be seamlessly used with Amaranth during design or verification; and Python development tools can process Amaranth code.
 
diff --git a/docs/reference.rst b/docs/reference.rst
new file mode 100644
index 0000000..4186ab5
--- /dev/null
+++ b/docs/reference.rst
@@ -0,0 +1,106 @@
+Language reference
+##################
+
+.. py:module:: amaranth.hdl
+
+.. warning::
+
+    This reference is a work in progress and is seriously incomplete!
+
+    While the wording below states that anything not described in this document isn't covered by the backwards compatibility guarantee, this should be ignored until the document is complete and this warning is removed.
+
+This reference describes the Python classes that underlie the Amaranth language's syntax. It assumes familiarity with the :doc:`language guide <guide>`.
+
+
+.. _lang-stability:
+
+Backwards compatibility
+=======================
+
+As part of the Amaranth backwards compatibility guarantee, any behaviors described in this document will not change from a version to another without at least one version including a warning about the impending change. Any nontrivial change to these behaviors must also go through the public review as a part of the `Amaranth Request for Comments process <https://amaranth-lang.org/rfcs/>`_.
+
+Conversely, any behavior not documented here is subject to change at any time with or without notice, and any names under the :mod:`amaranth.hdl` module that are not explicitly included in this document, even if they do not begin with an underscore, are internal to the implementation of the language.
+
+
+.. _lang-importing:
+
+Importing syntax
+================
+
+There are two ways to import the Amaranth syntax into a Python file: by importing the :ref:`prelude <lang-prelude>` or by importing individual names from the :mod:`amaranth.hdl` module. Since the prelude is kept small and rarely extended to avoid breaking downstream code that uses a glob import, there are some names that are only exported from the :mod:`amaranth.hdl` module. The following three snippets are equivalent:
+
+.. testcode::
+
+    from amaranth import *
+
+    m = Module()
+
+.. testcode::
+
+    import amaranth as am
+
+    m = am.Module()
+
+.. testcode::
+
+    from amaranth.hdl import Module
+
+    m = Module()
+
+The prelude exports exactly the following names:
+
+.. must be kept in sync with amaranth/__init__.py!
+
+* :class:`Shape`
+* :func:`unsigned`
+* :func:`signed`
+* :class:`Value`
+* :class:`Const`
+* :func:`C`
+* :func:`Mux`
+* :class:`Cat`
+* :class:`Repl` (deprecated)
+* :class:`Array`
+* :class:`Signal`
+* :class:`ClockSignal`
+* :class:`ResetSignal`
+* :class:`Module`
+* :class:`ClockDomain`
+* :class:`Elaboratable`
+* :class:`Fragment`
+* :class:`Instance`
+* :class:`Memory`
+* :class:`Record` (deprecated)
+* :class:`DomainRenamer`
+* :class:`ResetInserter`
+* :class:`EnableInserter`
+
+
+.. _lang-srcloc:
+
+Source locations
+================
+
+Many functions and methods in Amaranth take the :pc:`src_loc_at=0` keyword argument. These language constructs may inspect the call stack to determine the file and line of its call site, which will be used to annotate generated code when a netlist is generated or to improve diagnostic messages.
+
+Some call sites are not relevant for an Amaranth designer; e.g. when an Amaranth language construct is called from a user-defined utility function, the source location of the call site within this utility function is usually not interesting to the designer. In these cases, one or more levels of function calls can be removed from consideration using the :pc:`src_loc_at` argument as follows (using :meth:`Shape.cast` to demonstrate the concept):
+
+.. testcode::
+
+    def my_shape_cast(obj, *, src_loc_at=0):
+        ... # additionally process `obj`...
+        return Shape.cast(obj, src_loc_at=1 + src_loc_at)
+
+The number :pc:`1` corresponds to the number of call stack frames that should be skipped.
+
+
+Shapes
+======
+
+See also the introduction to :ref:`shapes <lang-shapes>` and :ref:`casting from shape-like objects <lang-shapelike>` in the language guide.
+
+.. autoclass:: Shape
+.. autofunction:: unsigned
+.. autofunction:: signed
+.. autoclass:: ShapeCastable
+.. autoclass:: ShapeLike
diff --git a/docs/start.rst b/docs/start.rst
index 0be3a76..84a9aa1 100644
--- a/docs/start.rst
+++ b/docs/start.rst
@@ -1,7 +1,7 @@
 Getting started
 ###############
 
-This section demonstrates the basic Amaranth workflow to provide a cursory overview of the language and the toolchain. See the :doc:`tutorial <tutorial>` for a step-by-step introduction to the language, and the :doc:`language guide <lang>` for a detailed explanation of every language construct.
+This section demonstrates the basic Amaranth workflow to provide a cursory overview of the language and the toolchain. See the :doc:`tutorial <tutorial>` for a step-by-step introduction to the language, and the :doc:`language guide <guide>` for a detailed explanation of every language construct.
 
 .. TODO: add link to build system doc
 .. TODO: add link to more complex examples?
diff --git a/docs/stdlib.rst b/docs/stdlib.rst
index 1f3ab73..2badf81 100644
--- a/docs/stdlib.rst
+++ b/docs/stdlib.rst
@@ -7,6 +7,8 @@ The :mod:`amaranth.lib` module, also known as the standard library, provides mod
 2. Modules that abstract common functionality whose implementation differs between hardware platforms. This includes :mod:`amaranth.lib.cdc`.
 3. Modules that have essentially one correct implementation and are of broad utility in digital designs. This includes :mod:`amaranth.lib.coding`, :mod:`amaranth.lib.fifo`, and :mod:`amaranth.lib.crc`.
 
+As part of the Amaranth backwards compatibility guarantee, any behaviors described in these documents will not change from a version to another without at least one version including a warning about the impending change. Any nontrivial change to these behaviors must also go through the public review as a part of the `Amaranth Request for Comments process <https://amaranth-lang.org/rfcs/>`_.
+
 The Amaranth standard library is separate from the Amaranth language: everything provided in it could have been implemented in a third-party library.
 
 .. toctree::


Test patch:
diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py
index a670f58..24f8441 100644
--- a/tests/test_hdl_ast.py
+++ b/tests/test_hdl_ast.py
@@ -167,8 +167,8 @@ class MockShapeCastable(ShapeCastable):
 class ShapeCastableTestCase(FHDLTestCase):
     def test_no_override(self):
         with self.assertRaisesRegex(TypeError,
-                r"^Class 'MockShapeCastableNoOverride' deriving from `ShapeCastable` must "
-                r"override the `as_shape` method$"):
+                r"^Class 'MockShapeCastableNoOverride' deriving from 'ShapeCastable' must "
+                r"override the 'as_shape' method$"):
             class MockShapeCastableNoOverride(ShapeCastable):
                 def __init__(self):
                     pass