Wado

WEP: Synopsis Tests

Context

Wado has two related but disconnected features today:

Test blocks are already permitted in implementation files (not only *_test.wado); the parser does not impose a filename restriction. What is restricted today is discovery: wado test only walks *_test.wado, so any test placed in an implementation file is silently ignored unless run by an explicit path.

Problem

Documentation without runnable examples rots. The Java/Javadoc tradition of hand-written, never-executed snippets is the canonical bad outcome — examples diverge from the API and readers learn to distrust them. Conversely, the perldoc SYNOPSIS section worked because it was short, hand-written, and placed at the head of the module — it served as the reader's entry point.

We want both properties at once for Wado:

Considered alternatives (rejected)

  1. Code blocks inside /// doc comments (Rust rustdoc doctests). Most powerful, but parsing Wado source inside a comment multiplies the number of front-ends to maintain: lexer, parser, formatter, syntax highlighter, and LSP would each need a "Wado-inside-comment" mode. The hidden-setup convention (Rust's leading-# lines) is a known anti-pattern: examples whose visible body lies are worse than no examples. Rust adopted this when its parser was already mature; Wado's parser is still evolving and the cost is prohibitive.

  2. Dedicated synopsis { ... } syntax (D unittest, Nim runnableExamples). Cleaner intent, but a synopsis is structurally a test: a runnable block with assertions. The only thing that differs is the documentation-rendering side effect. New syntax adds parser/formatter/LSP work for no semantic gain over reusing the test machinery.

Decision

The #[synopsis] attribute

A test block may carry #[synopsis]. Such a test:

//! Geometric primitives.

#[synopsis]
test {
    let p = Point { x: 0, y: 0 };
    let q = Point { x: 3, y: 4 };
    assert p.distance(&q) == 5.0;
}

pub struct Point {
    pub x: i32,
    pub y: i32,
}

impl Point {
    pub fn distance(&self, other: &Point) -> f64 { ... }
}

Rules:

Per-item binding (#[synopsis(for = some::path)]) is intentionally not introduced in this WEP. It can be added later if demand appears. Keeping synopses module-scoped avoids the "string path silently rots on rename" failure mode and matches the level at which perldoc readers actually decide whether to use a module.

Universal test discovery

wado test discovery is changed from **/*_test.wado to **/*.wado (with the same project-root and ignore rules currently in effect).

For each discovered file:

The historical *_test.wado naming is no longer required by the runner. It remains a recommended convention for files that contain only tests, as a human-readable signal.

This change is necessary to make #[synopsis] useful: a synopsis must live next to the code it documents (the implementation file), and that file must be visited by wado test for the synopsis to be verified.

Consumers of #[synopsis]

wado test treats a synopsis test as an ordinary test: it executes, participates in the regular pass/fail total, and is filterable by the existing --filter mechanism. No separate axis or summary line is added. The attribute is purely a tag.

The attribute's special meaning is interpreted by other tools:

Doc rendering

wado doc adds a ## Synopsis section per module, immediately after the module's //! doc and before per-item documentation:

## Synopsis

```wado
let p = Point { x: 0, y: 0 };
let q = Point { x: 3, y: 4 };
assert p.distance(&q) == 5.0;
```

The code is taken verbatim from the test body, using the source span between the outermost { and } of the test block. Indentation is normalised so the inner code starts at column 0.

A module without any #[synopsis] test produces no Synopsis section. Multiple #[synopsis] tests in one module render as successive code blocks under the single ## Synopsis heading, in source order.

wado doc does not execute any code; it renders synopsis bodies unconditionally. Freshness is the responsibility of wado test, and CI is expected to gate on wado test so that no broken synopsis reaches a published build.

Why this design

Why attribute, not new syntax

test, #[expect_trap], #[TODO], and #[timeout_ms] already form a coherent vocabulary: the test block is the unit, attributes change its reporting and pass/fail interpretation. #[synopsis] extends the same axis (it changes the rendering destination, not the structure). Adding a parallel synopsis { ... } keyword would duplicate machinery for a single-bit distinction.

Why module-scoped, not item-scoped

The perldoc SYNOPSIS that motivated this WEP is module-level by design. It is the place a reader looks first to decide whether to read on. Per-item examples are valuable but secondary, and they introduce binding-by-string-path fragility. Starting module-scoped keeps the design minimal and matches the documented goal.

Why universal .wado discovery

The current *_test.wado glob is a convenience, not a principle. Synopsis tests must live in implementation files, so the runner must visit those files. Compiling no-test files at the same time costs little and surfaces broken sources earlier — a positive side effect.

Why no hidden setup

Synopsis quality requires that the visible body be the entire example. A synopsis whose visible code lies because of hidden preamble is worse than no synopsis. The body the reader sees is the whole synopsis.

Consequences

User-visible

Implementation

Trade-offs

TODOs