I prefer Module#descendants because Class would automatically inherit Module#descendants but the converse does not hold.
I also believe Module#descendants is a more natural API than Class#descendants, because descendants should ideally be the dual of Module#ancestors; i.e., y ∈ x.descendants ⟺ x ∈ y.ancestors. Defining it at the Module level also answers the design question left open when [Feature #14394] was reverted (https://bugs.ruby-lang.org/issues/14394#note-33).
Assuming that Module#descendants is the dual of Module#ancestors, it should include self in its return value. However, I implemented Module#descendants not to include self, following ActiveSupport's Class#descendants and the reverted implementation of [Feature #14394]. Singleton classes are not included either, following the documented behavior of Class#subclasses. Note that [mod, *mod.descendants] can be used when self is needed.
The implementation reuses the existing subclass lists maintained for method cache invalidation, so no additional bookkeeping is introduced. Like Class#subclasses, the receiver does not hold strong references to its descendants, so the result may change after GC. Refinements are not included.
One use case of Module#descendants is Aspect Oriented Programming (illustrative, not a working example):
A similar approach could also be used by instrumentation libraries such as OpenTelemetry and Datadog's dd-trace-rb.
Note that we have to use hooks like inherited or included to patch classes which will be loaded later.
Module#descendants is also useful for diagnostics, e.g., finding all classes a monkey patch is prepended to, or all models that include a specific Rails concern.
This settles the question I left open in #14394. Class#subclasses already lets you write the transitive closure for classes in Ruby, but there is no equivalent primitive for modules, so Module is where this method actually adds something. Defining it there also gives Class the method for free.
Not including self is fine too. It departs from ancestors, but it agrees with Class#subclasses and with ActiveSupport's descendants, and [mod, *mod.descendants] is available when needed.
Once this is merged, I think #14394 can be closed.
Module#descendants returns an array of classes and modules that have
the receiver in their ancestors; it is the inverse of Module#ancestors.
The receiver itself, singleton classes, and refinements are not included.