Repository navigation
sysl 0.0.155 — a by-name parameter is a call inside a closure too
A parameter passed by name is now read the same way everywhere in its function: inside a closure, inside a nested function, and in the body after either one. Each read evaluates the argument. One spelling that 0.0.154 accepted is now refused (below).
Behaviour changes
Calling a by-name parameter inside a closure or nested function is now refused. In 0.0.154 that was the only way to read one there. Write the bare name instead, as the body always required.
f(x: -> int) -> int
val g = () -> x() + 1 // 0.0.154: compiled. 0.0.155: refused.
g()
error: type 'int' has no method 'call'
The fix is to drop the parentheses: val g = () -> x + 1. In the function's own body, x() was always refused with this message. A closure is now treated the same way.
What 0.0.154 refused and now compiles:
- A bare read inside a closure or nested function.
val g = () -> x + 1was refused with'+' needs '$F0: sysl.Add'and then'+' needs matching types, got a closure and int. - A bare read in the function's own body, when it comes after any closure or nested function.
val g = () -> 1followed byx + g()was refused with'+' needs '$F0: sysl.Add[int]'. With the closure removed, the same line compiled.
Every other program compiles to the same thing as on 0.0.154. Nothing in the org wrote x() inside a closure. A grep of every repo under sysl-lang, and of slate-language, found no by-name parameter in any package or program. The only matches are parse-test strings in the self-hosted compiler. So nothing needed fixing for this release.
Fixes
A by-name parameter read in a closure or nested function is a call (020a727)
reference/types.md § A parameter passed by name says: "Each use is an evaluation, because each use is a call." Inside a closure or nested function in the body, the compiler did not follow that rule. A read there gave the thunk itself, not its value.
There were two causes, both in one mechanism. analyzeNested calls resetFunction, which cleared byNameLocals. Nothing marked the captured names as by-name again, so the closure's body read the thunk. Nothing restored the set afterwards either, so the function's own reads after a closure stopped being calls as well. Now the by-name mark follows a captured name into the closure's scope, and is restored when the closure ends (FunctionBodies.scala).
Building the closure's environment has to capture the callable itself, not call it. captureRead in Closures.scala does this for a closure's fields and for a nested group's addresses. So the argument is evaluated at each read inside the closure, and never at capture.
Tests
ByNameTests: 8 new cases. All of them failed before the fix.
- a closure of the body calls it
- a nested function of the body calls it
- a closure evaluates the argument at every call of it
- a nested function evaluates the argument at every read
- a closure that outlives the call evaluates the argument when it is called
- a closure inside a nested function reaches it through both
- the body still calls it after a closure is written
- calling it inside a closure is refused, as it is in the body
Documentation
reference/types.md § A parameter passed by name on sysl.sh has a new runnable block. In it, a closure reads a by-name parameter and is called twice, and the argument runs once per call, printing 11 and then 21. One sentence was also added: the rule holds inside a closure or nested function too. DocsTests runs the block, so the fixed behaviour stays pinned.
Install
brew install sysl-lang/tap/sysl # or: brew upgrade sysl
The tarballs for macOS arm64, Linux x86_64 and Linux arm64 are attached to this release. The sh.sysl:sysl_3:0.0.155 jars are on GitHub Packages (https://maven.pkg.github.com/sysl-lang/sysl-bootstrap). sysl is not published to Maven Central.
Verification
- Native gate on the tagged tree (a34479b, which is 020a727 plus the version bump): GREEN, 12,290 succeeded and 0 failed. That is 416 suites in 46 chunks plus the groups that run alone. Nothing timed out and nothing was retried. The gate covers syslNative and syslDocNative. It took 57:04, after a cleaned warnings census. The fix had landed on a targeted gate, so this is its first full run.
- Warnings census, cleaned: JVM two, JS three, Native three, syslDocJVM one, syslDocNative two. That is the expected count, and every warning belongs to a dependency or is build infrastructure.
- Release tarball: extracted to a scratch prefix and run.
sysl --versionprintssysl 0.0.155, andsysl-docis present. The 0.0.154 reproductions now behave as described above.val g = () -> x + 1prints42.x + g()after a closure prints42.x()in a closure is refused withtype 'int' has no method 'call'. The new sysl.sh block prints11,21,2. - GitHub Packages: all four artifacts (the pom, the jar, the sources jar and the javadoc jar) answer 302.
- brew:
brew test syslpasses, and the installedsysl --versionprintssysl 0.0.155. The Linux glibc floor, measured in the release run, is 2.34 on both architectures. - sysl.sh on 0.0.155: 1,416 tests pass with 0 failures. Of those, 1,396 are DocsTests. The new block was checked by mutation: changing its expected output to
11 11 2, which is what evaluating the argument once at capture would print, turned DocsTests red on exactly that block (types.mdprogram 26). Restoring it turned the suite green again. - Org sweep with the release tarball's own binary: 77 builds, 71 green under the bare command.
- 54 ran under
sysl test ., 12 undersysl build .and 5 undersysl build-c <dir>. - Four repos fail the bare command by design and were run as their READMEs say.
freertosagainst a freshly built FreeRTOS-Kernel POSIX port: 84 passed.libpqagainst a scratch PostgreSQL: 51 passed.quickjs-ngwith its include path: 33 passed. pico:build-lib --target thumbv6m-freestandinggets as far as the pico-sdk's generatedcyw43_arch.h, which means the sysl type-check passed.pico2andzephyrrefuse the bare command by design, through theirrequires { headers }clause.- Not swept, being kernel or board repos that need a toolchain or SDK this machine does not have:
zephyr,picokit,ogol-pico,ogol-pico2,solder-pico2,zephyr-demo,pico-scratch. Also not swept, being infrastructure:sysl-bootstrap,sysl(the self-hosted compiler),sysl.sh,homebrew-tap,github-profileandsvd.
- 54 ran under