Repository navigation
iyi 0.2.0
A program chooses its library. 0.1.0 had one, 1,184 lines of it, and that
was most of what stood between the language and anybody's real program. It
turned out not to be a library problem: a prelude is a library and the rules
are the language, so a program can keep one and change the other. --crystal
does, and there require means what it means in Crystal. Nine shards were
swept through it and a Kemal server written in iyi serves HTTP; how many of the
rest work is not something this release measured.
Two more entries take the rules further out — pub reaches a macro and a
constant — and closing each found the same hole underneath: a surface nobody
wrote and nobody could refuse.
Master is 0.2.0-dev, and under the artifact rule below that means every build
of it interoperates with nothing but itself. That is the point: a version
between two releases names no compiler.
Changed
-
The tarball carries Crystal's library, so
--crystalworks in what people
download. It shipped iyi's own 56 KB and nothing else, so the release's
headline feature answeredrequire "json"with "can't find file" outside a
checkout. It ships both now — 13.4 MB to 14.9 MB — and CI runs a--crystal
program out of the unpacked tarball, which is where this was found and where
it would have been found again. -
make cli_specsays once when the daemon and the compiler are different
builds. The daemon refuses a client built from another compiler, correctly
— it holds an analysed prelude — but the spec saw that as nine failures, each
printing two version strings, with the reason in none of them. It is easy to
arrive at, too: the build commit comes from git HEAD while make compares file
times, so a commit can leave two binaries disagreeing about a commit while
agreeing about every line of code. -
iyi mod diffsays whether a change reaches a module's consumers. The
three hashes an artifact carries already answered it and nothing asked them.
It compares two.iyimodfiles, says which of interface, implementation and
source moved — with what each of the three means, because the middle one is
the surprising one — and names the exports that came and went when the
interface is what moved.--exit-codeexits 1 in that case, which is
git diff's spelling and its reason: the answer is not a failure. -
An iyi program is run on three targets every build, not one. It compiled
for eight and was tested on one, which is a weak thing to call portability.
CI now cross-compileshello.iyifor musl and for aarch64, links each with
the target's ownccandlibgc— the command--cross-compileprints —
and runs them: in an Alpine container and under emulation. The check is that
each prints what the same program printed on the machine that compiled it. -
bench/runtime.pymeasures what the library costs at run time. The two
libraries are within noise where they do the same work;Hashis 6x ahead
and does less;Stringis 1.64x behind. The first reading said string
building was twenty times faster, and it was the collector — a 17 KB binary
has fewer roots to scan than a 972 KB one — so the bench reports both columns
and the honest one is the second. -
iyi describes itself as its own language, compatible with Crystal. "A
language built for Developer & Agentic Experience, Portability, Performance,
and Efficiency", and README says what stands behind each of the four and what
does not: the edit loop and the artifact are built, portability means eight
targets that compile and one that is tested, the run-time measurement is new
and says the two libraries are within noise where they do the same work, and
the agentic claim is a mechanism rather than a result.Compatibility is stated as something checkable and in one direction: the same
compiler builds.crfiles, an iyi program canrequirea shard with
--crystal, and a Crystal program cannot require an iyi module, because R-2's
written types and R-3's closed types are what an artifact is made of.iyi versionreadsiyi 0.2.0-dev (built on Crystal 1.22.0-dev …): the
language first and what it is built on after. The licence and NOTICE.md are
unchanged, because Apache 2.0's attribution is an obligation rather than a
description. -
An artifact is read by the release that wrote it, not by the build. A
.iyimodwas locked to the exact compiler build, commit and all, so two
builds of the same version refused each other's modules and handing one to
somebody meant handing them your compiler too. The identity is now the
released version, the target and the flags: every build of iyi 0.1.0 reads
every other build's artifacts on the same target under the same flags. A
-devversion keeps the commit, because it names no release. The version
comes fromsrc/IYI_VERSION, which is also what the binary reports and what
the tarball is named after.
Added
-
iyi build --crystalcompiles a program against Crystal's standard
library, sorequirereaches the ecosystem. A.iyifile refused
requirebecause there was nothing to require: the prelude is what a program
gets. That reason stops being true when the prelude is Crystal's. The rules
do not change with the library — the module header,pub,import,using,
traits andimplare all still there, and a shard is ordinary Crystal
compiled into the program.A Kemal server written this way serves HTTP. What it gives up is R-1 for that
dependency: the shard is read from source, so the edit loop pays for it the
way Crystal does. Your own modules are unaffected, and--emit-iyimodstill
writes them.crystal tool bindwrites what it generates with--emit-bind, its own
switch, because what it writes is a boundary for a shard rather than this
build's own modules.The two libraries are two modes and do not mix on the artifact side:
--use-iyimodand--emit-iyimodneed iyi's own prelude. An artifact's
object code numbers the types its module made, which under Crystal's library
include the standard library's own, and a consumer compiling its own copy of
that library has two of everything. That was an LLVM module which would not
verify; it is a sentence now.Nine shards were swept through it, each built twice — as an iyi program
and as a Crystal one, so that a difference is this fork's and a shared
failure is the ecosystem's.kemal,db,ameba,habitat,
baked_file_system,radix,sqlite3, the standard library's own
json/yaml/uri/http, and a program that round-trips
JSON::Serializableand writes a file. All nine behave the same in both
languages. One needed a word changed and it was the rule working:habitat's
macro resolves the type it is handed by name, and a class an iyi module
leaves unmarked is private, so it needspub class.samples/crystal/stdlib.iyiis the program CI builds to keep this true: a
trait with a default, animplon a generic, an error union with!and
.or, adefer, and JSON, YAML and URI in the same file. -
pubtakes a constant.pub LIMIT = 42is reachable through the
module's name; an unmarked constant is the module's own and is refused by the
sentence an unmarkeddefgets. Nothing was added to the artifact format,
because a module's top level already travels as source and the mark travels
with it.The hole under it is the same one
pub macrofound: a constant's visibility
was never set, so every constant a module declared was reachable. That is
what a real shard leans on — Kemal hands out every object it has through one. -
pub macro, so a macro can cross a module boundary. Every macro a module
writes already travelled in its artifact, because a body that travels may
call one, but none of them was reachable:pubdid not take a macro. A
marked one is now reachable exactly as apub defis, unqualified after
usingor through the module's name, and it works against an artifact with
the module's source deleted. What it exports is a name and an arity, because
a macro takes syntax and returns syntax.Two things worth knowing. Closing this found the hole under it: a macro's
visibility was never set, so every module's macros were already callable
through its name — that is refused now, with the sentence an unexporteddef
gets. And macros are not hygienic, so apub macrothat writestmp = 99
assigns to the consumer'stmp; SPEC.md IV.4 says so in full. -
iyi tool formatformats iyi. The formatter is Crystal's and knew none
of iyi's syntax, so a module header, apub, atrait, animpl, a
using, a boundedforall, awhere, adefer, a!or an.orsent it
into "there's a bug formatting this file". It knows all of them now, and a
directory is searched for.iyifiles as well as.crones. The prelude and
the nine samples format to themselves, which is the test that made the last
three of those show up.
Fixed
-
A generic imported from an artifact links again when its type argument is
inferred.Box(Int32).new(42)always worked andBox.new(42)did not.
Inference makesnewa method onBox(T), which is the artifact's own type,
and the rule that says "this type's machine code is in the artifact" was
reading the generic itself. An artifact carries a unit for every non-generic
type a module declares and none for a generic one, because a generic has
machine code only once somebody picks its arguments, and the consumer is who
picks them. So the consumer declared anewthat nothing defined and the
program failed to link, sayingundefined reference to Box(T)::new<Int32>.
The consumer's rule now matches the producer's. Reported after 0.1.0 went
out. -
Array#sortsorts the array, andsortedhands back a copy. SPEC.md
III.1.7(A) settled that pair — the plain verb mutates, the participle copies,
Swift's convention adopted because!had to leave identifiers so postfix
!could propagate an error — and the prelude did not implement it:sort
returned a copy and nothing mutated. It does now, andsortedis one line
over it.Worth knowing when moving between the two libraries: Crystal names the same
pairsort!andsort, soa.sortcopies there and sorts here. It is the
one call in this prelude that means something different under--crystal,
and the note is insrc/iyi/array.iyiwhere somebody is standing when it
matters. -
A
usingthat cannot deliver is refused where it is written. A module
header makes a type, and inside it that name means the module — so
using app/count::{Tally}in a module calledtallyasks for a name it
cannot have. What it said before was thatTallywas not
App::Count::Tally, at the first line that used one, with nothing pointing
at the directive. Found by writing a command-line program. -
String#sizecounts when nobody counted. Crystal reads@length == 0on
a non-empty string as "not counted yet" and scans; this prelude returned the
field. A string built by Crystal's ownto_jsontherefore printed correctly
through iyi'sputsand answeredsize0 — no error, a wrong number.
Free for every string this prelude makes, because it fills the field. Found
while measuring what crosses between the two languages. -
A macro that cannot see a type says why. An unresolved path stays a
Path, so every method a macro would call on the type is undefined on that,
and the message namedPathrather than the type or the rule:
undefined macro method 'Path#constant'. It now asks whether the path names
a type that exists and is unexported, and says so when it does. Found by
habitat. -
The front end reads a
.iyifile again. Refusingrequirein a.iyi
file spares the prelude's own, and the flag that says so was set in the
driver but not incrystal-front, so the front-end binary refused the line
it had just written itself. It is bench-only and ships in nothing. -
A version bump takes effect.
src/VERSIONandsrc/IYI_VERSIONare
compiled in withread_file, and the build did not depend on them, so
changing the number changed nothing until something else did.