Onion Practical-Quality Bar¶
"Practical quality" is intentionally vague, so this file pins it to a set of objectively measurable indicators. Each row has a measurement you can run and a threshold; the language has reached the bar when every row passes.
Baseline figures are the ground-truth values as of 2026-07-26 (develop @ c2126299), re-measured rather than carried forward — the previous baseline (2026-07-26 @ 871fe5a1) had already drifted after the effect-table / tool-capability / tool-contracts work (#356,
357, #358) landed: it recorded 2590 tests against an actual 2644, 14/14 guides against¶
15/15 (docs/guide/tools.md shipped with #357), and 77 diagnostic codes against 80
(E0077–E0079 added by the capability boundary).
| # | Dimension | How to measure | Current (approx.; see note below) | Pass threshold |
|---|---|---|---|---|
| 1 | Test suite | sbt shutdown && sbt -Duser.language=en testFull (see the note below) |
5669 pass / 0 fail / 1 cancelled | 0 failed, 0 skipped |
| 2 | Sample health | SampleCompilesSpec / SampleProgramsSpec (both compile every run/*.on) |
389 / 389 compile | all compile, no rot |
| 3 | Large programs | count of run/*.on ≥ 100 lines that run end-to-end as-is |
322 (see run/ for the full, alphabetized list — no longer enumerated here, see note below) |
≥ 5 |
| 4 | Feature coverage | checklist below demonstrated inside the large samples | complete | every item ✓ |
| 5 | Known usability bugs | implemented-but-unreachable / broken features still open | 0 | 0 |
| 6 | Docs parity | docs/guide vs docs/ja/guide count + every code block compiles |
15 / 15 | parity + all blocks verified |
| 7 | Diagnostics | distinct E00xx codes with EN+JA messages |
90 | every common error has a dedicated code |
Rows 2 and 3 are checked against a tolerance band, not exact equality (issue #1370).
Every PR that adds a sample to run/ used to have to bump the exact counts in both rows
and insert its sample's name into row 3's alphabetized list — and because every such PR
touched the same two lines, any two of them landed within an hour of each other conflicted,
which is exactly what let 29 of them pile up unmerged before anyone noticed. QualityBarSpec
now only fails when the recorded figure is either ahead of reality (an outright lie) or has
drifted more than a fixed band behind it, the same philosophy row 1's test count already
used. A sample addition no longer needs to touch this file at all; only a periodic top-up
of the two numbers does, once drift approaches the band.
This fix previously shipped as #1372, then was silently reverted when a stale branch (#1336) was merged with an older copy of this file, which is why it needed reapplying here.
Do not set SBT_OPTS to a heap below the project default. .jvmopts pins the
default to -Xmx10g (raised from 4g as the run/ sample corpus grew — see
CHANGELOG 0.10.18). Any SBT_OPTS heap lower than that — -Xmx2g was one
previous bad recommendation, -Xmx4G is another one seen in the wild — lowers
the effective heap below what the full suite needs and makes it die with an
OutOfMemoryError partway through (see #691). If you need to override
SBT_OPTS for another flag, keep the heap at -Xmx10G or higher.
-Duser.language=en matters: error messages are bilingual and resolved from the JVM
default locale, so a test asserting on message text passes in a Japanese locale and fails
in release CI, which runs in English. Assert on error codes, not localized text.
Two sbt 2 behaviours make that easy to get wrong locally, and both fail quietly:
testis incremental. It delegates totestQuick, so running it a second time with no source change printsNo tests to runand exits 0 — which reads exactly like a pass.testFullruns everything.-Donly reaches a fresh server. The sbt server persists across invocations, so-Duser.language=jahanded to a server already started underenchanges nothing;java.util.Locale.getDefault()keeps reporting the old locale.sbt shutdownfirst.
Together those mean the obvious sbt -Duser.language=en test followed by
sbt -Duser.language=ja test can report two green runs having tested one locale once.
CI is unaffected: the incremental state lives in target/, which setup-java's
cache: 'sbt' does not cache, so every CI run starts cold.
The one cancelled test is the distribution smoke test, gated behind -Donion.dist.path.
Practical quality is reached when rows 1–7 all pass. This turns the open-ended goal "reach practical quality" into a checkable state.
Row 4 — feature coverage checklist¶
A feature counts as covered once it runs inside at least one large sample
(run/), not just a micro-test:
- records (plain) and data-carrying enums
- plain enums
- classes with constructors and methods
- interfaces + polymorphic dispatch (FleetManager)
- top-level
defwith block and expression bodies - recursion (incl. tail position)
- collection pipelines: map / filter / fold / reduce / sortedBy / groupBy / find / distinct / partition / zip / flatten
-
select/ pattern matching -
if/else ifexpressions -
while,foreachover ranges and overMap(k, v) - nullable types with null checks
- closures stored in vals
- string interpolation
#{} - try / catch
- generics used non-trivially in a large sample (
StatsApp—SafeBox[T],Pair[A,B], genericcountMatches) - extension methods used in a large sample (
StatsApp,ShapeProcessor,TextAnalyzer,TodoManager)
Row 5 — currently open usability bugs (tracked)¶
None open.
Previously tracked and resolved:
- Primitive-type extensions — fixed.
extension Int { def double(): Int = self * 2 }and(5).double()now work. Extension methods on primitive receivers are registered under the boxed class name and the call target is unboxed before invoking the backing static method. - Top-level function called from a class method — fixed. Top-level
val/varand functions are emitted as static members of the synthetic top-level class, and bare identifiers / unqualified calls in class methods fall back to these static members. - Constant narrowing does not reach constructor arguments.
val b: Byte = 100narrows, butnew R(..., -3)against aShortcomponent wasE0021("constructor applicable for R(..., Int) is not found") and needed(-3 as Short). Fixed for constructors (#374); the same gap in ordinary method/function overload resolution (takesShort(-3)against aShortparameter, reported asE0005) was found and fixed alongside it — both now share theConstantNarrowinghelper.