Repository navigation
Soundness 0.74.0 — the jars of 295 library modules, with their Scala.js and Scala Native
cross-builds — 717 assets in all, each embedding its own POM and ivy.xml.
Changes
Rename parameters which share a name with a method (#2336)
Fuel documents a parameter as a member of whatever it parameterises, so a type or constructor parameter with the same name as a method of its class merges two entities in the API docs. This renames the parameter in each of the ten cases from #2334 where consumers can't observe that name. In escritoire, row is the type-parameter name throughout the library, so the method Layout#row is renamed to tableRow instead. Resolves #2334.
Release notes
- escritoire:
Layout#row(cells, decorations)is nowLayout#tableRow(cells, decorations). Its behaviour is unchanged. - The following type parameters were renamed. Code that passes type arguments by position is unaffected.
Attempt[success, error]→Attempt[result, error]Scalac[version, universe]→Scalac[release, universe]Url[scheme]→Url[protocol]Proxy[key, value, id]→Proxy[key, value, ordinal]Rootable[root]→Rootable[degree]Submission[value]→Submission[content]Dictionary[value]→Dictionary[meaning]Interaction[result, question]→Interaction[answer, question]
Fix the benchmarks, gate them in attest, and drain the suites' stdlib bridges (#2335)
Four pieces of the #2235 plan, bundled after #2333. (1) The six benchmark modules — acyclicity, hypotenuse, kaleidoscope, parasite, scintillate and ulysses — had stopped compiling on main, invisibly, since benchmarks are in neither soundness.all nor the attest: a shared Array has no Int apply (that belongs to an exclusive, inline receiver), so the loop-indexed reads in parasite's and ulysses's harnesses become readUnchecked with the loop as the proof; the read-only array fields in kaleidoscope and hypotenuse are declared frozen (^{}), which is what their constructors produce and what lets an object hold them without becoming a capability; acyclicity's cached Edges hold frozen Soundness arrays rather than mutable JVM ones, whose capturing refinement the dataset cache could not store, and hand the rival DAGs JVM copies across the .stdlib bridge; parasite's agreement rows cross the same bridge; scintillate's rival server reads its header with head. And so that this cannot recur, the attest now runs benches.compile (compile only, a few minutes). (2) The 231 .stdlib.head, .stdlib(i) and .stdlib.slice reads in the suites — the dysasymptotic gate bypasses the plan's B2 names — become the adapting head with the assertion carried through its Optional (xs.head.let(…), .lay(default)(…)), at behind the linearAccess gate, segment(a.z till b.z) on a block of bytes, seek where a find(…).get stood, occupied where a fixture is proven once and then read totally, and readUnchecked with the loop as its proof where a counting loop reads an array; three stay, with their reason beside them (wisteria's confined reads the checker cannot narrow, embarcadero's capturing layer). (3) The mechanical stdlib shapes in the suites — map, exists, forall, count, find, filter, flatMap, reverse, grouped, take, drop, toList, contains, headOption, last, zipWithIndex, zip, foreach — become the native names, a stdlib value converting with List.from/Set.from/Chain.from rather than the factory form of to; the plain core bridges in stratiform's Tels and Telp, syndesis and the TypeScript renderer follow (seek for find(…) match, where for indexWhere, join for mkString). What stays is deliberate and documented: a Chain of frozen arrays flattened (the native flatMap trips capture checking on Array[Byte]^{any.rd} elements), collect/sorted chains with no native twin, Option-valued APIs, the grouped chunking helpers that feed a stream, frozen-array surgery in stratiform.Mutation with no native updated/splice vocabulary yet, and the macro/TASTy files the plan tags as the reflect boundary. The census falls from 1463 .stdlib bridges on main to 1024. (4) Flare counts the .stdlib bridge as a rule, so the trend lands in the git notes commit by commit; make build's while baseline is resynchronised with main.
For users: nothing changes in the libraries' public behaviour; stratiform's Telp and Tels resolve references the same way through seek; the TypeScript renderer prints the same text. No migration entry.
Restore the opacity of the collections through the soundness umbrella (#2333)
Found while landing #2330: export proscenium.List (and Sequence, Set, Map, Chain) in the soundness umbrella compiles to a def forwarder, and a companion member selected through that unstable prefix sees the opaque alias's right-hand side — so List(1, 2).mkString, Sequence.empty[Int].toVector, xs.iterator, xs.take(n), xs.foldLeft, xs.length and the rest of the stdlib API compiled on any value built through the umbrella, invisible to the .stdlib ratchet. The umbrella now forwards each collection as a parameterised type alias plus a stable val — a bare alias of an opaque constructor is not seen through by ::, CanEqual or the extension methods, and the parameterised one is — and the five companions extend caps.Pure, because a val of a non-pure object type is a tracked capability that xs.to(List) captured into its result. Restoring opacity exposed what the leak had been papering over on the library side, each fixed where it belongs: the Populated equalities live in the collection companions (CanEqual is sought in companions, and a brand contributes none — the rudiments givens had only ever worked where they were lexically visible); larceny's demilitarize returns the native list; jacinta's *-spread unbrands List[t] & Populated; the Iterable-receiver extensions (sift, bi, tri, longestTrain, mean, variance, std, median, race, survive) gain Traversable-receiver twins; Holidays.apply and Watch.apply take any Traversable; dendrology's render returns the native list. Sixty-nine test suites are drained to the native names (join, keep, fold, all, has, nil, size behind the gate, List.iterator, List.concat, indexed) or to List.from at a stdlib boundary; the Java and macro boundaries (xenophile's ArrayList, querencia's foreign AST) keep .stdlib and raise their census rows. The while baseline is resynchronised with main, which had drifted (attest does not run the ratchets). The six benchmark modules fail on main independently of this change (values(i) on an Array with an Int, Array.from typing, hypotenuse's Capability fields) and are left for their own fix.
For users: nothing on a native collection changes except that the stdlib API is no longer reachable through import soundness.* — xs.to[List] on a native value still works; a stdlib value converts with List.from(xs); and demilitarize(…) is a native List, so .nonEmpty becomes !….nil. Entries under "soundness (umbrella)", "larceny", "rudiments", "hypotenuse", "parasite", "contingency", "jacinta", "aviation", "surveillance" and "dendrology" in doc/migration/0.74.0.md.
Accept a Sized block wherever a plain one is, and export Sized from the umbrella (#2332)
Follow-up to #2330. A Sized[n] value is a refinement of its carrier type, and a typeclass instance declared for the plain carrier does not match it, so a proven block fell out of size, at, last, posting, serving, transmitting, reading and Protobuf encoding. The fix is the one ruled for #2235's proofs — the instances, not the sites: Countable, Applicable and Terminable gain the per-brand sizedFrozenArray instance Traversable already had, and the argument-position Data typeclasses — telekinesis.Postable/Servable, coaxial.Transmissible, turbulence.Readable (to Data and Text) and locomotion's Encodable in Protobuf — gain sized… twins. The encoding is per-brand (the brand spelt in the Self) rather than a subtype-parametric widening, because a type variable bounded by a capturing type is elaborated as array^{any.rd} and then fails evidence declared for the pure array; doc/standards/loops.md records the rule for a new Data is X given: twin it if Self is only an argument, not if Self is a result or a stream element (Addressable, Computable, Ingressive, Framable stay exact). A probe compiled with import soundness.* against the umbrella showed that Sized[n] did not resolve for consumers at all: proscenium.Sized was never exported, and the name went to tasseomancy's doughnut node-extent typeclass, now Weighted (def weight). Also drains the .stdlib.head #2331 added to ultimatum's suite, which had left the escape census failing on main.
For users: Data.fill(16)(_.toByte).digest[Sha2[256]], .in[Protobuf].encode, .read[Text], .size, .last and .at(i) now resolve on the proven block; Sized[512] works under import soundness.*; a given Step is Sized = _.size for a doughnut becomes given Step is Weighted = _.size. Migration entries under "proscenium (Sized)" and "tasseomancy" in doc/migration/0.74.0.md.
Draw a bar's partial boundary cell in the fill's colour (#2331)
A smooth bar's partly filled boundary cell is now drawn in the fill's colour, not the leading edge's. A partial is part of the fill, so it should match the cells to its left; drawn in leadingEdge, it made a smooth bar look as if it ended in a tip of another colour (cyan on blue, under solarizedDarkGaugePalette).
- A
Bar.Filleddesign's partial glyph (▏▎▍▌▋▊▉inbars.smoothBar) takesGaugePalette#fill, or, on a gradient design,GaugePalette#lengthwiseat the cell's position. - A design's
tipglyph ([===> ]), aBar.Marker's head andStanding.RunningkeepleadingEdge: they mark where the fill has got to, not how much of it there is. - The boundary cell keeps the track's background, as before.
Add Sized, a compile-time proof of array length, and build with Proscala 1.0.5 (#2330)
Second step of the #2235 plan's proof mechanisms: fixed-layout data — a 512-byte tar header, an 8-byte AXML header, a JPEG marker pair — is read at literal offsets whose safety the length guarantees, and nothing in the types said so. proscenium.Sized[n] is a phantom length proof in the style of Populated: Array.fill(n)(…), Array.tabulate(n)(…), Data.fill(n)(…) and Data.build(n)(…) with a literal n mint it (a compile-time constant only — a val length does not brand), Array.concat of two sized arrays yields Sized[n + m], and xs.sized[n] checks a Countable value's length once and records it, as occupied does for non-emptiness. On a Sized[n] array, xs(i) with a literal ordinal (156.z, Prim…Sept) is total and a literal at or beyond n is a compile error; a variable ordinal still yields Optional. The macro behind the read (rudiments.sizedMacros, in concordance) reads the literal from the inlined tree, so it works through inline proxies, inside lambdas, and through the umbrella's export forwarders. Traversable, Encodable in Data and Streamable gain a Sized instance, so to[List], serialize and streaming resolve on a proven block as on a plain one; a medium typeclass (Addressable, behind stream, Cursor(…), Stream(…)) is deliberately not widened, since a brand on a medium is meaningless, so a Data meant as a medium names its type. The byte-probe tests in bitumen, pneumatic, hallucination, enigmatic and geodesy now read through it. The build moves to Proscala 1.0.5 (upstream base unchanged at Scala 3.10.0).
For users: Array.fill(512)(0.toByte) is now Array[Byte] & Sized[512] and header(Prim) on it is a Byte, not Optional[Byte]; header(600.z) does not compile; a length known only at runtime is proven with bytes.sized[512].let: header => …; Cursor(Data.fill(10)(_.toByte): Data) names the medium. doc/standards/loops.md has a "Proven lengths" section; the migration entries are in doc/migration/0.74.0.md under "proscenium (Sized)".
Package with xek 1.4.0 (#2329)
Moves to xek 1.4.0.
Replace stdlib round trips and partial reads with native, proof-carrying forms (#2326)
First sweep of the #2235 clean-up plan on the .stdlib bridge: 1865 → 1514 bridges (stdlibRound 317 → 163, stdlibGate 464 → 269), with every library, benchmark and test module compiled from a clean checkout and the full suite green (17655 passed, rebased onto #2325).
The round trips — Sequence.from(x.stdlib), List.from(x.stdlib.toList), x.stdlib.toList.map(…) — become x.to[Sequence]/x.to[List]. The dysasymptotic gate bypasses in the test suites — xs.stdlib.head, .length, .isEmpty on a native List — become head, size with the linearSize gate opened explicitly, and nil. Where an operation guarantees non-emptiness the type now says so: Text#cut returns a Populated list (spelt List[textual] { type Occupied = true }, so case List(a, b) still binds), as do List.prepend/append, Fqcn#parts and Searchpaths.Stems#stems, and head/last are total on them. head, lead and tail adapt to provable populatedness as last already did (the element when proven, Optional otherwise), and prim/sec/ter — the unproven head under another name — are removed. Showable.list is subtype-parametric so a branded list still shows; the infix :: stays unbranded (a right-associative chain cannot infer its element from a branded right operand — use List.prepend). xs.head match replaces xs.stdlib.head match in the suites, and if !xs.nil && xs.stdlib.tail.isEmpty then …xs.stdlib.head becomes xs match case block #:: rest if rest.nil => ….
For users: xs.prim → xs.head; xs.sec → xs.at(Sec); xs.tail on an unproven receiver is now Optional (.tail.or(Nil) keeps the old empty-stays-empty); cut's limit ≤ 0 reads as unlimited, the generic textual is Cuttable by Regex instance returns the pieces between matches, and a case List(a, b) on a cut result needs typed binders (case List(a: Text, b: Text)) in the current compiler. Details in doc/migration/0.74.0.md under proscenium, gossamer, digression, galilei, denominative and rudiments.
Found along the way and left for a follow-up: the umbrella's export proscenium.{List, Sequence, Chain, Set, Map} compiles to def forwarders through which the opaque collections are transparent — the whole stdlib API is reachable on anything built via soundness.List(…), uncounted by the ratchet. A stable-forwarder fix restores opacity; its fallout (~300 sites in 69 suites, and several library extensions on Iterable receivers) is its own PR.
Derive a sum at a member position as a record holding a SelectRef (#2327)
A derived schema typed every sum-typed member as a Reference to the sum's select, which TEL forbids (E217, §20.3), so no record with an enum-typed field could be written as BinTEL (#2323). A field's type can only name a definition (§20.6), and the issue's suggested inline struct can't be written as schema source or hashed. So a sum-typed member now names a <Sum>Value record holding one SelectRef to the sum's select. That is exactly what the derived codecs already write: the field's keyword with one variant beneath it. Closes #2323.
Release notes
- stratiform: a field whose type is a sum
Figurinederives asfield shape FigurineValue, whererecord FigurineValueholdsselect Figurine. The same applies toList,Set,OptionalandMapelements and to select variants.- The select keeps the sum's name, and a sum used only at the root is unchanged, so its schema hash stays the same.
- Types with sum-typed members gain the
…Valuerecords and a new schema hash. Those schemas were invalid before and couldn't round-trip through BinTEL. - A user type named
<Sum>Valuebeside a sum<Sum>is now an E210 clash.
enum Figurine:
case Circle(radius: Int)
case Square(side: Int)
case class Drawing(shape: Figurine, shapes: List[Figurine])
Bintel.read[Drawing](Drawing(Figurine.Circle(1), List(Figurine.Square(2))).bintel)
// Drawing(Circle(1), List(Square(2))), where it failed with SN-605.217Pyrocosm's workaround (propensive/pyrocosm#76) can be removed once it moves to a release with this change.
Raise every provider-record failure through the caller's tactic (#2325)
A record built by Json.Provider, Xml.Provider or Tel.Provider threw its failures past the caller's tactic: schema violations in jacinta (whose fail took no tactic), and, in every format, absent required fields, wrong-typed values and values of a kind no union alternative offers. So attempt, validate and track couldn't see them (#2294). Now every field reads through the caller's Tactic, so a validating reader can collect every problem in a document.
Release notes
- polyvinyl:
- A
Specificationdeclarestype Failure <: Hazard. access,required,repeated,entries,elementsandpairstake(using Tactic[Failure]), and the new hookunofferedhandles a union kind no alternative offers.- Every field reads as
Record.Field[T, Failure], a pure context function applied where the field is selected. - An
Intensional.FalliblewhoseErrorisn't a subtype ofFailureis a compile error. - The new
Intensional.Readingmakes a fallible reader from a function.
- A
- jacinta:
- Absent and wrong-typed fields raise
Json.Provider.Error(MissingValue(name),JsonType), where they raisedJson.Error. - New reasons:
UnexpectedKindandUnparsable.
- Absent and wrong-typed fields raise
- xylophone: a value its type can't read raises
Xml.Provider.Error, with the new reasonMalformed. - stratiform: new
Tel.Provider.Error(SN-614), withAbsent(name)andNotSigil. - apoplexy: array responses need a
Tactic[Json.Provider.Error]whererecord()ortuple()expands.
object S extends Json.Provider(cp"/schema.json")
val json = t"""{"primitive": "nonsense"}""".read[Json]
attempt[Json.Provider.Error](S.record(json).primitive)
// Attempt.Failure(Json.Provider.Error(NotPermitted(nonsense, [ae, mac])))Exact details are in doc/migration/0.74.0.md.
Fold the .stdlib ratchet into the escape census, covering tests (#2324)
The .stdlib bridge was gated by a shell script that kept one aggregate number over core sources only, so the test suites — which hold 1187 of the 1859 bridges — could grow freely, and it missed the . stdlib continuation form. etc/check-escape-count.py now ratchets the bridge per file over core and test sources alike (benchmarks excepted, since rival-side code uses it on purpose), with shape columns that read the residue as a queue — the dysasymptotic gate bypasses (stdlibGate), the round trips (stdlibRound) and stdlibIterator — plus collectionImport for the direct import scala.collection lines roadmap core-3 counts. --stdlib lists the residue by what follows the bridge. etc/check-stdlib-count.sh and its baseline are removed; make build runs the census only. First step of the #2235 clean-up plan. The baseline is seeded against main after #2321, whose seven new .stdlib.toList round trips in stratiform's typed acceptance tests are the first thing the gate would have caught; they are left for the round-trip sweep.
No library code changes. For contributors: a new .stdlib in a test file now fails make build like one in a library; prefer the native API, or open the dysasymptotic gate explicitly (import denominative.dysasymptotics.linearAccess, with a comment) when the cost is the point. python3 etc/check-escape-count.py --stdlib shows the remaining uses by shape.
Name derived TEL definitions by type, qualifying clashing names (#2321)
A schema derived by stratiform for two types sharing a simple name silently kept only one definition, so writing a value under it failed with SN-605.217 and a value of the dropped type was checked against the wrong definition (#2319). Derived definitions are now identified by their type and named together over the whole schema: an uncontested name is unchanged, every member of a clashing group is qualified by its enclosing type, an unresolvable clash is E210 at derivation, and the assembled schema is checked as a hand-written one is.
Derived schemas name clashing types by their enclosing type
Two types reached from one root that share a simple name each get their own definition, qualified by the enclosing type, in whichever order the schema reaches them:
object Alpha:
case class Item(label: Text)
object Beta:
case class Item(count: Int)
case class Both(alpha: Alpha.Item, beta: Beta.Item)
Tels.tels[Both](t"both").records.map(_.name) // AlphaItem, BetaItem
Bintel.read[Both](Both(Alpha.Item(t"one"), Beta.Item(2)).bintel) // round-tripsA type whose name no other type shares is named as before, so existing schemas, their hashes and BinTEL signatures are unchanged. A clash the qualification cannot tell apart is reported as E210 (Tel.Error.Reason.DuplicateDefinition) when the schema is derived, rather than as a type-assignment failure when a value is written.
Tels.tels raises Tel.Error
Tels.tels, Tel.Acceptance.lineage, lineages, Tel.Acceptance[…]() and .otherwise now require a Tactic[Tel.Error]. The TelSchematic derivation protocol threads a TelSchematic.Naming and is keyed on TelSchematic.Identity; see doc/migration/0.74.0.md.
Note: the issue's own reproduction (Sheet/Element/Phrase) still fails at write time for a separate, pre-existing reason — a product with a sum-typed field derives to Field(Reference(<select>)), which type assignment rejects as kind-mismatch even without any clash (Envelope(id: Text, message: Message) fails the same way). That needs its own design decision and issue.
Check every library with Flare 0.10.0 (#2322)
Every library is checked as it compiles by the Flare compiler plugin, dev.propensive:flare-plugin 0.10.0, configured by the -P:flare: options in flareToolchain and by .pyrocosm/flare/config.tel. The unsafety census (make unsafety) runs flare metrics, and make plumbing runs flare assess plumbing, which records its verdicts under refs/notes/flare-assess/plumbing.
Bound and speed up frontier's explanation of a missing given (#2320)
A missing given could take hours to explain in a large scope, because three costs multiplied together (#2318). Frontier's search over classpath proposals sat outside its budget. Stenography redid its scope work and rebuilt every type tree on each call. The rendered tree had no size limit, and joining its lines was quadratic. This PR bounds the search, caps the output, makes the join near-linear and caches stenography's scope-independent work for each compiler run. Closes #2318.
Release notes
- frontier: the
explainMissingContextexplanation now charges its classpath proposals to the same budget of 256 as its searches.Diagnostic.rendershows at mostlimitlines (new parameter, default 100), followed by… n more, and writes a repeated subtree once, then asas above. - stenography:
Syntaxtrees no longer depend on the call site. Arrows (->/=>) and infix type aliases are chosen whentextruns, from the new fieldImports#pureFunsandImports#aliases.Bindingstakes no parameters. A refinement's alias member is stored as a bareSyntax, not aDeclaration.internal.namecaches trees and scope summaries for each compiler run, andDesignators are interned.// before given Bindings = Bindings(pureFuns = false) syntax.text(using imports) // after syntax.text(using imports.copy(pureFuns = false))
- escapade: joining many
Teletypes now takes O(n log n) copying, not O(n²), and gives the same result.
See doc/migration/0.74.0.md for exact details.
Migration
doc/migration/0.74.0.md
records every rename, move, signature change, removal and changed behaviour a consumer could
observe from 0.73.0 to 0.74.0. It is written for an LLM agent rather than for a person: to
upgrade a project from version A to version B, instruct an agent to read every
doc/migration/<v>.md with A < v <= B, in ascending version order, and to apply the changes
each one describes.
Assets
295 library modules, each attached as <artifactId>-0.74.0.jar, alongside the Scala.js
(_sjs1_3) and Scala Native (_native0.5_3) cross-builds of the platform-capable components,
with its POM and ivy.xml embedded under META-INF/maven/ — the jar alone is enough for a
consumer to resolve it. Depend on this release by pinning it in etc/refs:
propensive/soundness 0.74.0
make sync-deps installs the set into a local ivy repository, transitively; see
propensive/.github for the scheme.