Repository navigation
Sprig v0.7.1-beta.1
Pre-releaseSprig v0.7.1-beta.1
Sprig v0.7.1-beta.1 is the second v0.7 Beta. It fixes what an evaluation
of v0.7.0-beta.1 found with real programs and Java baselines: for over
range no longer builds a list, top-level code with several hot loops runs as
fast as the same Java, a value that may be null no longer slips into text as
null, an Error shows its message wherever it becomes text, Strings gain
compareTo, and help and sprig capabilities now match what the compiler
does. Java calls also get easier: an Int goes into an int parameter with a
run-time range check, and wildcard types in Java signatures are read at their
bounds instead of being rejected. sprig api now shows the comment written
above each declaration. It is an
experimental Beta, not a promise of production stability or a frozen API. The
compiler is 0.7.1-beta.1; the language version remains 0.8-dev. JDK 17+
is required. Linux and macOS are release-supported; Windows remains an
experimental, non-blocking preview.
This is the first release under the release and versioning
policy, which says what each of the two version numbers
means and when it changes. Under that policy this is a PATCH release, and the
language stays 0.8-dev: the new Java interop accepts programs that were
rejected before, the changes that reject code accepted before are soundness
fixes, and the one change to what a compiling program prints is a behavior
fix. The upgrade section lists all of them.
Fixes
for over range counts instead of building a list
for i in range(n) used to build the whole list before the first iteration:
30,000,000 iterations held about 1 GB, and a larger bound ran out of memory. A
for loop over range(...) is now a counted loop with constant memory. The
bounds are evaluated once, from left to right, a zero step fails with the same
Error as before, and each iteration has its own variable, so a lambda still
captures one value per iteration. range(...) used as a value is still a
MutableList[Int].
Top-level hot loops as fast as Java
A script that ran several hot loops in its top-level code got slower every
round: the same 200-million-iteration loop took 100, 210 and then 465 ms, while
the equivalent Java stayed near 100 ms. Every operand of a string join went
through a general formatter that the JIT inlines, and in one long top-level
block those copies used up HotSpot's inlining budget, so a later loop's checked
arithmetic stayed a real call. Strings, Int, Int32, Float, Float32 and
Bool now join with Java's own concatenation. The same benchmark runs in 100,
113 and 115 ms, and every kind of value still joins to the same text.
A value that may be null is not joined into text
let count = "x".toIntOrNull()
print("count " + count) # v0.7.0 printed "count null"
Joining a T? into a String, with + or +=, is now SPR-TYPE-NULLABLE.
The hint shows the repair for the operand you wrote: check it with
if count != null:, copy a var into a let first, bind an expression such as
prices["tea"] to a let (only a let narrows), or give a fallback with
or_else from @std/nulls.spr. A toString() result from Java is now a
non-null String, as Object's contract says, so "at " + date.toString()
keeps compiling.
An Error shows its message everywhere
try:
throw Error("disk full")
catch problem: Error:
print(problem.toString()) # v0.7.0 printed "sprig.runtime.SprigError: disk full"
print(problem) and "failed: " + problem already showed the message, but
problem.toString() returned Java's text with the runtime's class name. It now
gives the message too, as toString() gives the printed text of every other
value. The same holds for the errors built on Error (BadRequest,
SqliteError) and for an Error caught as RuntimeException. A Java
exception keeps Java's text, class name first, whichever way it becomes text.
A Java exception's message is now a String?, because Java's getMessage()
may return null; "cannot read: " + problem.message printed cannot read: null for such an exception. An Error's message stays a String. Reading
message from the type itself (Error.message) passed sprig check and then
failed in javac; it is now SPR-JVM-MEMBER, and that message names the type
Error rather than SprigError.
String.compareTo for Java comparators
a.compareTo(b) returns -1, 0 or 1 in the order < uses for Strings, as
an Int32, which is the int a Java Comparator returns:
names.sort(fn(a: String, b: String) => a.compareTo(b))
A comparator lambda that returns an Int works as well; see Java interop
below.
== on classes is written down
== compares numbers, Bool, String, enums, variants, lists and maps by
value, and two class objects by identity: two objects whose fields match are
still two objects. That was already the behavior, but help language said
"== and != compare values" and nothing mentioned classes. The rule is now in
help language, help classes, help variants, the quick reference and the
language tour, and a runtime test keeps it. Classes keep identity on purpose:
a class can have var fields, so two objects equal now can differ after one
changes, a field-by-field == would walk whole object graphs (and loop on
cycles), and a map or set key compared by contents breaks when the key
changes. When you want a value compared by its contents, use a variant, or
compare the field that identifies the object, such as a.id == b.id.
Help and capabilities
sprig capabilitieslisted varargs and "arbitrary Java SAM" as unsupported
while its JSON saidjvmVarargsandjvmFunctionalInterfacesaretrue.
The lists now name what works (Java varargs calls, Sprig function values as
functional interfaces with up to three parameters,Comparable,rethrows)
and the exact remaining limits, and a test keeps them consistent with the
flags.- A nullable operand that isn't a plain name, such as
m["k"], used to get
the hint "check it first with 'if the value != null:'", which isn't code you
can write. It now says to bind the expression to aletand shows the line. String? + String?reportsSPR-TYPE-NULLABLEinstead of a numeric-mixing
error.m[key] += 1on a map typed with a type parameter (MutableMap[K, Int])
inside generic code passedsprig checkand then failed injavac; it now
runs.toString()works on a Java value typed by an interface, such as theList
thatsubListreturns.
Java interop
Int into int
An Int now goes into an int or Integer Java parameter, constructor
argument, varargs element or field with a run-time range check, so computed
values no longer need .toInt32Exact(). A value outside the Int32 range
fails as the usual numeric error. A Sprig function passed as a Java callback
may return an Int where Java expects an int:
names.sort(fn(a: String, b: String) => a.length() - b.length())
let index = 2
print(names.get(index))
An exact long overload still wins (Math.max(a, b) keeps the long
version). Int into short or byte (literals only), Float into float
(precision, not range) and compound assignment to an int field stay
explicit.
Wildcards
Wildcard types in Java signatures are no longer rejected. A result reads at
its upper bound (a List<? extends Entity> gives Entity? elements), a
parameter accepts any value that fits the bound
(addAll(Collection<? extends E>), Class<?>), and a write through
? extends is rejected with SPR-JVM-MEMBER naming the capture. A
? super X slot takes X. Sprig itself still has no wildcard syntax: a
wildcard-typed value can be held and passed on, not written in a declaration.
A public Java field with a generic type is now read with its type arguments,
as sprig api already reported.
Tooling
sprig api module.spr --json includes doc, the # comment written directly
above each declaration and member, the same text editor hover shows. The text
output prints it under the signature as # ....
Upgrade from v0.7.0-beta.1
On a managed Linux/macOS installation, run sprig upgrade --check and then
sprig upgrade. The installer verifies the published checksum and archive
before switching SDKs; older installed versions remain available. A manual
installation can use the ZIP and .sha256 file from the
release page.
Then run sprig resolve in each project root. A lock records the compiler
that wrote it, so a lock from v0.7.0-beta.1 is reported as
SPR-PROJECT-LOCK-STALE.
Two changes can reject code that compiled before. Both are soundness fixes,
reported as SPR-TYPE-NULLABLE:
-
A
T?value cannot be joined into aString. Each place is reported at
the+. Check the value first, or give it a fallback:import "@std/nulls.spr" as nulls let count = "x".toIntOrNull() print("count " + nulls.or_else[Int](count, 0))A value returned by a Java method counts as possibly
null, so
"year " + LocalDate.of(2024, 1, 2)is rejected too: bind the result to a
let, check it, then join it. AtoString()result is aStringand joins
as before. -
A Java exception's
messageis aString?. Join the exception itself
("IO error: " + problem, which shows Java's text with the class name), or
check the message first. AnError'smessageis still aString.
One change makes a program that still compiles print something else. It is a
behavior fix, and no diagnostic reports it:
toString()on anErrorreturns its message, without the
sprig.runtime.SprigError:prefix; likewise forBadRequest,SqliteError
and anErrorcaught asRuntimeException. Search for.toString()on a
caught error, such as"ERR:" + problem.toString(). Code that stripped the
prefix can drop that step. Java code that turns anErrorinto text still
sees the prefix.
A public Java field with a generic type is now read with its type arguments
instead of its raw class, which can change the type of such a read. Apart from
these, programs that compiled before and still compile behave as before.
Known limits
The v0.7.0-beta.1 limits still apply,
except that wildcards in Java signatures are now read at their bounds.
Recursive and intersection bounds (such as Collections.max), type-variable
varargs and generic arrays remain unsupported. Top-level statements run once,
as one JVM method that HotSpot compiles while it runs; a long script with
several hot loops shares that one compilation, so keep hot loops in
functions.
See docs/language/known-limitations.md, docs/language/feature-status.md,
and the versioned output of sprig capabilities --json before relying on a
particular feature. Every change since v0.7.0-beta.1 is listed in the
comparison.