ADR 0008: Doc Comments and API Documentation

Status

Implemented (2026-02-15)

Context

Beamtalk's standard library lives in 26 stdlib/src/*.bt files that were recently converted from documentation-only stubs to compilable source with @primitive pragmas (BT-293, ADR 0007 Phase 2). During conversion, verbose API documentation headers were removed, leaving concise class comments and method signatures as the only documentation.

The problem: There is no structured way to document methods, classes, or modules in Beamtalk source code. The language has // line comments and /* */ block comments, but no doc-comment syntax that tooling can extract for:

Current state:

Constraints:

Decision

Adopt /// triple-slash doc comments for class and method documentation, following the Gleam/Rust convention.

Method Documentation

Object subclass: Integer
  /// Add two integers. Returns the sum.
  ///
  /// Supports arbitrary precision — no overflow.
  ///
  /// ## Examples
  /// ```beamtalk
  /// 3 + 4       // => 7
  /// 100 + -50   // => 50
  /// ```
  + other => @primitive '+'

  /// Absolute value of this integer.
  ///
  /// ## Examples
  /// ```beamtalk
  /// -42 abs   // => 42
  /// 7 abs     // => 7
  /// ```
  abs => (self < 0) ifTrue: [self negated] ifFalse: [self]

Class Documentation

/// Integer - Whole number arithmetic and operations
///
/// Integers in Beamtalk are arbitrary precision (Erlang integers).
/// All arithmetic operations return integers unless explicitly
/// converted with `asFloat`.
///
/// ## BEAM Mapping
/// Beamtalk integers map directly to Erlang integers.
///
/// ## Examples
/// ```beamtalk
/// 42 class           // => Integer
/// 2 ** 100           // => 1267650600228229401496703205376
/// 17 % 5             // => 2
/// 1 to: 5 do: [:n | Transcript show: n]
/// ```

Object subclass: Integer
  // ... methods

REPL Help

beamtalk> :help Integer +
Integer >> + other

Add two integers. Returns the sum.
Supports arbitrary precision — no overflow.

Examples:
  3 + 4       // => 7
  100 + -50   // => 50

beamtalk> :help Integer abs
Integer >> abs

Absolute value of this integer.

Examples:
  -42 abs   // => 42
  7 abs     // => 7

Error Example (Misuse)

// Regular comments are NOT documentation
// This will NOT appear in :help or LSP hover
+ other => @primitive '+'

/// This IS documentation — appears in tooling
- other => @primitive '-'

Generated Documentation

beamtalk doc generates HTML reference documentation from /// comments:

beamtalk doc lib/           # Generate docs for stdlib
beamtalk doc src/           # Generate docs for user project
open docs/index.html        # Browse generated reference

Prior Art

LanguageSyntaxFormatRuntime AccessTooling
Gleam/// method, //// moduleMarkdownNogleam docs
Rust/// item, //! moduleMarkdownNorustdoc
Elixir@doc, @moduledocMarkdownYes (Code.fetch_docs)ExDoc
Erlang (OTP 27+)-doc, EDocMarkdown/HTMLYes (EEP-48, code:get_doc)shell_docs
Pharo"..." class/method commentsPlain textYes (comment protocol)Browser
Newspeak(* ... *)Plain textNoNone
Python"""...""" docstringsreStructuredTextYes (doc)Sphinx

What we adopted:

What we adapted:

What we rejected:

User Impact

Newcomer (from Python/JS/Ruby)

/// is immediately recognizable — it's the same convention as Rust, TypeScript (/** */), and similar to Python docstrings. They'll expect :help in the REPL and LSP hover. The ## Examples section with // => mirrors E2E test format, reinforcing a single pattern.

Smalltalk Developer

Pharo uses "..." comments for class/method docs. The /// syntax is a departure but follows Beamtalk's existing choice of // over "...". Smalltalk developers expect browsable documentation — the beamtalk doc command and LSP hover provide this. The REPL :help command mirrors Pharo's browser experience.

Erlang/Elixir Developer

Elixir developers will miss @doc and runtime reflection. However, /// with Markdown is familiar from many languages, and generated HTML docs are the expected output. The beamtalk doc command mirrors mix docs. Runtime doc access (:help in REPL) provides the interactive experience Elixir developers expect.

Tooling Developer

/// is the simplest to implement — it's a lexer-level distinction (check for three slashes vs two). The trivia system already preserves comments on tokens. Extending the AST to attach doc comments to Class/Method nodes is straightforward. The LSP hover provider just reads the attached doc string.

Steelman Analysis

Strongest Counterarguments Against This Decision

1. "Docs are the wrong investment right now" (Priorities)

Beamtalk has no type system, no pattern matching, no package manager, and an incomplete stdlib. Writing doc infrastructure — lexer changes, AST extensions, EEP-48 codegen, chunk injection, REPL :help, HTML generation — before the language stabilizes means documenting APIs that will change. Every hour on /// parsing is an hour not spent on language features that users actually need. Gleam shipped versions 0.1–0.30 without doc tooling and it was fine. Ship the language first, add docs when the API surface is stable.

Why we accept this cost: The ADR establishes design decisions, not delivery timelines. Deciding on /// now means every new stdlib method gets docs from day one — retrofitting docs onto 200+ methods later is worse than growing them incrementally. The implementation phases (1–6) are deliberately sized so Phase 1 (lexer + AST) is small and can ship early without blocking language work. We're not building beamtalk doc HTML generation before pattern matching — we're recording the syntax decision so stdlib authors write /// comments today.

2. "EEP-48 can't express doc inheritance" (Architecture)

When someone asks :help Counter, they should see Counter's own methods and inherited methods from Actor and Object. But EEP-48 docs are per-module — each .beam file contains only its own docs with no concept of class hierarchy. Building :help that walks the inheritance chain means building a doc resolution system on top of EEP-48, not just mapping into it. This is a second doc system — one that understands Beamtalk's class hierarchy, method resolution order, and overriding. The EEP-48 chunk becomes a storage format, not a query system.

Why we accept this cost: The inheritance walk is already implemented for method dispatch (beamtalk_object_class:find_method/2). Doc inheritance reuses the same infrastructure — walk the class chain, call code:get_doc/1 on each module, merge results. This is ~50 lines of Erlang, not a second system. Elixir's IEx.Helpers.h/1 does the same for behaviours and protocols. The per-module storage is correct — each class documents its own methods. The REPL :help command composes them at query time.

3. "/// locks you in before the language can express docs" (Premature Commitment)

Beamtalk might eventually have string interpolation, rich text objects, structured annotations (@metadata), or Newspeak-style module declarations. Choosing /// now means docs are always flat Markdown strings parsed from comments — a design from the 1990s. If the language later gets first-class annotation syntax, the doc system can't use it without a breaking change or migration. Starting with /// is easy, but it may become the legacy format that every future feature has to work around.

Why we accept this cost: /// is a syntax choice, not a semantic commitment. The AST stores doc_comment: Option<String> — any future annotation system can populate that same field. If @doc attributes arrive later, the compiler can accept both /// and @doc (like Erlang accepts both -doc and EDoc comments). Markdown-in-comments is the dominant industry pattern (Rust, Gleam, Go, Swift, Kotlin) — it's not 1990s design, it's current best practice. The risk of premature commitment is real but small: /// is trivially parseable and any migration tool can convert /// to @doc mechanically.

4. "Post-erlc BEAM rewriting is fragile" (Engineering Risk)

The plan says "inject docs post-compilation via beam_lib:build_module/2." But Beamtalk shells out to erlc for Core Erlang → BEAM compilation — it doesn't control the full pipeline like Elixir does. Adding a post-processing step that rewrites .beam files means: (a) debugging compilation failures has another suspect, (b) the build pipeline can't be a simple erlc invocation anymore, (c) future OTP versions could change BEAM file internals, (d) the chunk injection step must handle every edge case erlc handles (compressed modules, native code, etc.). This is fragile plumbing for a young compiler.

Why we accept this cost: beam_lib:build_module/2 is a stable OTP API specifically designed for chunk manipulation — it's not low-level file surgery. Elixir, Gleam, and LFE all use similar post-processing for their metadata. The BEAM file format has been stable for decades and OTP guarantees backward compatibility for beam_lib. The post-processing step is isolated and testable: generate chunk data → call one OTP function → write file. If it becomes problematic, an alternative is to generate a companion .docs file and teach the REPL to load docs separately (but this loses EEP-48 interop).

Residual Tension

Alternatives Considered

Alternative: @doc Pragma

Object subclass: Integer
  @doc 'Add two integers. Returns the sum.'
  + other => @primitive '+'

Rejected because /// + EEP-48 achieves the same runtime accessibility without a new pragma. The @doc syntax would add parser complexity, and the doc string would need its own flow through codegen — whereas /// trivia naturally attaches to AST nodes and compiles to EEP-48 doc chunks alongside existing codegen.

Alternative: Convention-based // Comments

Object subclass: Integer
  // Add two integers. Returns the sum.
  + other => @primitive '+'

Rejected because there's no way to distinguish documentation comments from implementation comments. The comment // Helper for overflow checking shouldn't appear in API docs, but // Add two integers should. Without a syntactic marker, tooling would either include everything (noisy) or use fragile heuristics.

Consequences

Positive

Negative

Neutral

Resolved Questions

  1. //// for module docs: Removed — just use ///. Beamtalk doesn't have modules (one class per file). Package-level docs belong in README.md. If modules are added later, a module doc syntax can be introduced then.

  2. Doc inheritance: Yes — walk the hierarchy. :help Counter spawn should show docs inherited from Actor >> spawn if Counter doesn't override the documentation, following Pharo's approach. This matches the existing respondsTo: hierarchy walking (ADR 0006) and provides better UX for users exploring unfamiliar classes.

  3. Structured parameter docs: Freeform Markdown only, following Rust and Gleam. Parameter names are inferred from the method signature. Use conventional headings (## Arguments, ## Examples) for structure. No @param/@returns tags — keeps it simple and avoids JSDoc-style syntax in a Smalltalk-inspired language.

  4. Doctest interaction with ADR 0014: Deferred. The doctest mechanism will be designed when implementing doctests. The /// ```beamtalk syntax is reserved for future use but the integration with ADR 0014's test framework is not decided here.

Implementation

Phase 1: Write Stdlib /// Docs (S)

Phase 2: Lexer + AST (S)

Phase 3: EEP-48 + REPL :help (M)

Phase 4: LSP Integration (S)

Phase 5: beamtalk doc Command (M)

Future: Live Doc Editing

Migration Path

No breaking changes. The /// syntax is new — existing // comments are unaffected. The stdlib files will need doc comments added incrementally (Phase 1).

Recommended migration order:

  1. Core types first: Integer, Float, Number, String, Symbol
  2. Collections: List, Dictionary, Set, Tuple, Association
  3. Hierarchy: ProtoObject, Object, Actor, Block, CompiledMethod
  4. Boolean/Nil: True, False, UndefinedObject
  5. Error hierarchy: Exception, Error, RuntimeError, TypeError, InstantiationError
  6. System: SystemDictionary, TranscriptStream, File

Implementation Tracking

Epic: BT-496 — Doc Comments and API Documentation Status: ✅ Done

PhaseIssueTitleSizeStatus
1 (parallel)BT-497Add /// doc comments to all 26 stdlib filesMDone
1 (parallel)BT-498Parse /// as doc comments and attach to AST nodesSDone
2BT-499Generate EEP-48 doc chunks in compiled .beam filesMDone
2BT-500Implement REPL :help command for class and method docsMDone
3BT-501Add beamtalk doc CLI command for HTML doc generationMDone

Dependencies: BT-497 + BT-498 → BT-499 → BT-500, BT-501 Related: BT-441 (doctest extraction)

References