Sigil 0.20.0
A dependency-declaration release, with one runtime fix that matters if you build to WASM. The sigil: field in package.sgl is now a minimum version rather than a range, so sigil: "0.16" means "needs at least Sigil 0.16" and stops expiring on its own.
Existing projects build without changes. Carets are grandfathered and keep working, with one warning per build. Locks are untouched and no project's runtime moves. The minor bump is because the field's grammar narrowed: a host manifest declaring ~, >=, <, = or * is now refused.
Why a floor, and why the old field expired by itself
A caret on a 0.x version pins the minor, so ^0.16 means [0.16.0, 0.17.0). That made every sigil: declaration expire by construction. A package that declared ^0.16 two releases ago was, by its own declaration, incompatible with 0.19 while running perfectly. Staleness was the resting state rather than a lapse.
The proof that the old reading measured the wrong thing is that the monorepo could not build itself under it. Twenty members declared ^0.17, five declared ^0.18.6, and sigil-cli at ^0.17 depended on four ^0.18.6 members. It built fine every day.
An upper bound is a prediction about software that does not exist yet; a floor is a fact about what you use. Predictions expire and facts do not. If you need a real ceiling you can now say so: sigil: (supports min: "0.16" max: "1.0"), with max exclusive.
Your existing carets keep working, and the tolerance removes itself
A caret with a floor below 0.20 is read as the bare floor, with one compact warning naming the file, the caret and the replacement. The caret is ignored rather than honoured, so a migrated manifest and an unmigrated one resolve identically. Every operatored declaration measured across the estate was a caret, all 2,858 of them, so refusing the others costs nobody anything while keeping the grammar closed against new spellings.
Two mechanisms end the tolerance without anyone having to remember. New caret debt cannot accumulate, because only floors below 0.20 are grandfathered and ^0.20 refuses today. And at Sigil 1.0 the tolerance ends entirely, because ^0.16 read as floor 0.16 must not admit 1.0.
Neither is behind an environment variable, deliberately. A mode switch is a deferred flip, and deferred flips do not happen.
Why the warning names a version
The warning tells you the minimum toolchain to be on before rewriting the value, and that is load-bearing rather than polite. A compiler predating this release reads a bare sigil: value as an exact range. Measured on a real repository: it evicts the lockfile's runtime pin and silently downgrades, exit 0.
So following the advice too early breaks your lock. A refusal never had that hazard, because seeing one proved your compiler was new enough. A warning travels to people whose compiler is not.
Relocking does not move your runtime
sigil deps install keeps the locked sigil-lib and sigil-stdlib even across the migration's range restatement. sigil deps update holds the runtime unless you pass --upgrade-sigil. Only a fresh install with no lockfile takes the newest admitted tag.
If you have been expecting a relock to pull a newer runtime line, it will not, and that is the intended behaviour rather than an oversight.
Macros now expand correctly in native and WASM builds
On a native-codegen build, null?, pair? and symbol? answered about a syntax object's wrapper rather than its datum. The bytecode VM and the C builtins both unwrap first, so the two backends disagreed and only native was wrong.
That matters because a procedural macro transformer is handed the marked syntax object and takes it apart with exactly these predicates. (sigil core)'s own extended-lambda transformer is one of them: its walk over the formals never terminates on (null? formals), calls car on a syntax-wrapped (), and the expansion dies with car: expected pair.
The fingerprint was an odd asymmetry:
(lambda () x) failed to expand (lambda (x) x) worked
because with a non-empty formals list car unwraps, and the next null? then sees a plain ().
If you only ship bytecode, nothing changed for you. The only native-codegen build that expands macros at runtime is a WASM app's eval door, so this reached browser apps that evaluate code the user types. Slate's scratchpad is the case that surfaced it.
Exactly three predicates were affected, and that is the whole class rather than the three we happened to notice. Of Sigil's seven syntax-transparent builtins, only null?, pair? and symbol? have an inlined native fast path; list? and syntax? already fell through to the builtin, and identifier? and keyword? have no CPS primitive at all. number?, string?, boolean?, procedure? and char? are opaque in both backends and stay that way.
Also in this release
- The
depscommand paths refused invalid manifests through a mechanism that does not survive native-codegen binaries, so a release binary printed the refusal and installed anyway. All four paths now check positively. - A lock carve-out could hold a dependency's sha across a URL change, which then failed worktree creation: a healthy install refused for a stale reason.
sigil: " 0.16 "passed validation, which trims, then fell through to legacy resolution, which did not, silently resolving an exact 0.16.0.(supports min: "0.16" max: "0.16.0")was accepted as a window admitting nothing.- A held runtime's "there is a newer tag" hint pointed at
sigil deps update, the command you may just have run. It now names--upgrade-sigil.
Upgrade notes
Nothing is required. Your carets keep working and will warn once per build.
To silence the warning, drop the operator: sigil: "^0.16" becomes sigil: "0.16". Do this only once every Sigil building the project is at least 0.20.0, which the warning states explicitly. An older compiler reads the bare value as exact and will evict your runtime pin.
If you genuinely need an upper bound, use the supports form rather than an operator.