Repository navigation
Releases: codyspate/khoralang
Release list
Khora 0.4.0
Coming from 0.3.0, read three groups of entries first.
What stops code compiling. A record field is private to its module
unless it is marked pub. Glob imports and a second with or raises
clause are removed. A bare case name in a pattern, a constructor pattern of
the wrong type and two instantiations of one error type in a row are
refused. A Region, a Scope and a Db stay on the fiber that opened
them, and a spawned fiber's error must be Share. Fiber::cancelled is
spelled Fiber::canceled. A handler for Db needs query_each, depth,
savepoint, release and rollback_to. A khora.toml that does not load
stops check, build and the other project commands, and three shapes
0.3.0 warned about are refused.
Wrong answers given with nothing to say so, fixed, so code that ran on
0.3.0 may behave differently: a postgres query could return another
caller's rows; std::db::transaction could lose writes it had answered
Ok; a transaction inside another one committed or rolled back the outer
one; a postgres transaction whose body ignored a failed statement was
answered Ok with nothing committed; the type checker let through a pattern
of the wrong type, a catch that yielded 0 and a raise that no catch
saw; a case name written bare in a pattern matched every value; a catch
arm could read a generic error's field at the wrong type; and a lambda
handed to scoped inside a function with a scope of its own released what
it acquired at the outer scope's end.
Crashes a server operator meets, each of them in 0.3.0: every Router
server died after about 130,000 connections; one client that reset its
connection killed the whole server with SIGPIPE; the otlp exporter
crashed every traced service after about 20,000 spans; a 31 KB request body
of nested brackets, or other long input, ran the stack out and ended the
server; and a fiber on the scheduler backend had an eighth of main's
stack.
Cancellation has its own channel. A canceled fiber stops at its next
cancellation point in every function, whatever the function's raises row,
which changes what SIGTERM, Fiber::join, Fiber::outcome and catch do
in code with no row. On the TechEmpower tests Khora serves more requests a
second than Go on JSON, as many on Fortunes, and fewer on the two database
tests (see Changed).
Breaking
-
A
postgresquery could return another caller's rows (see Fixed). A
program that ran with a receive deadline, or on a network that dropped
reads, may have acted on answers that belonged to other queries. The
connection that caused it is closed instead of reused, and its pool slot
reconnects it (see the pool entry below). -
std::db::transactioncould lose a write it had acknowledged withOk
(see Fixed). Atransactionwhose fiber is canceled before its body
finishes is rolled back, including one canceled whileCOMMITwaited for
its reply, which changes nothing if the commit ran. ADbhandler has a
rule to follow for it; see theDbhandler entry below. -
A
transactioninside atransactioncommitted or rolled back the
enclosing one (see Fixed). An innertransactionis a savepoint: its
writes are committed with the enclosing transaction's or not at all, and
its failure undoes only its own writes. -
A
postgrestransaction whose body ignored a failed statement was
answeredOk(see Fixed). It is answeredDbError::RolledBack. -
A bare name in a pattern that is the name of one of its value's cases is
refused, inmatch,catch,letandfor, at any depth, and for
cases with a payload as well as without.Red => ..over aColorbinds
every color; the message names the pattern to write,Color::Red
(FsError::NotFound(_)for a payload case, and the type's module where
the file never imported it). A bare name that is a type's only case, as
incatch { Stop => () }over a recordStop, matched what the case
would have and is refused as well: write_. The check is against the
value's type, so a case added to a type later makes a binding of the same
name an error rather than a match. A capitalized bare name that is no case
of the value's type is refused as well: a name in a pattern that starts with
a capital letter must be a case. The message reads like an undefined
name and offers the nearest case, "Colorhas no caseGren. Did you
meanColor::Green?", when one is within two edits (or a third of the
name, if that is more); the language server offers it as a quick fix. A
constwritten as a pattern (FAVORITE => .., which binds rather than
compares) is told to use a guard,n if n == FAVORITE. Bind with a
lower-case name. 0.3.0 built all of these with anunused-binding
warning at most, and gave a wrong answer (see Fixed). -
A constructor pattern of one type, matched against a value of another,
is refused, at any depth, inmatch,catch,letand a pattern inside
a tuple or record pattern: "this pattern is aResultcase, and the value
here is aBig". This includes a pattern over a type parameter ("... the
value here is aT"). Where both types were held on the heap, 0.3.0 built
the program and read the field out of the wrong constructor (see Fixed). -
(raise X) catch { X => 7 }is anInt, the type its arms produce.
0.3.0 typed it as the operand's, which never finishes, so it fitted
wherever a value was wanted and yielded 0 there (see Fixed). A use that
fitted only because of that is refused:
let s: String = (raise X) catch { X => 7 }. -
A
raiseof a value whose type is worked out later is charged to the
catches and the row around it, as araiseof a value of known type
is. On 0.3.0 such an error went past everycatch(see Fixed). Programs
this refuses:- the error escapes a closure's
catchand the closure is called without
!: "kcan leave this function, so the call needs!"; - the value turns out to be a second instantiation of an error type the
catchbeside it names: "acatcharm handles one instantiation of a
type, and this operand raises two; catch them with_, or in two
catches". A program of this shape whose other branch was the one that
ran happened to give the right answer, and is refused all the same; - the value's type is never worked out at all: "the type of this raised
value was never worked out"; - the value turns out to be an error type that the closure's row, already
fixed by where the closure is passed (attempt,retry, a parameter
written() -> Int raises Nf), does not carry: "thisraisesendsDn,
and the closure it is in had its error row closed to{ Nf: Nf }, which
does not carry it", naming where the row was fixed and suggesting an
annotation. Another closure calling this one beside aDnof its own
does not count: the row has to carry it.
0.3.0 built these and crashed, or, where the raising branch never ran or
the error happened to reach acatchthat named it, gave the right answer. - the error escapes a closure's
-
An error type appears in a
raisesrow at one instantiation. An arm
that reads a field of a generic error at a type other than the one raised
is refused; on 0.3.0 it read the wrong type (see Fixed). So is anything
that would put two instantiations of one error type in one row, wherever
they meet:- one
catchover(if b { fi()! } else { fs()! }), withfiraising
Gx<Int>andfsraisingGx<String>, when it has aGx::..arm or an
arm that binds the failure (catch { e => .. }), including a_beside
aGx::..arm; - a closure whose body raises both, however it is then used: called,
passed toattemptor a row-polymorphic function, or given to
Fiber::spawn; raises E + Finstantiated withEandFtwo instantiations of one
type;- two different error types that share a name, declared in two modules
(anm::a::Eand anm::b::E), wherever the same routes put them in
one row. An error row is labeled by the type's bare name, so the two
collide as two instantiations do; a named arm there read one type's
field as the other's on 0.3.0. The message says they are two types of
one name.
One arm reads one layout, and the program cannot say which instantiation
arrived. Catch each where it is raised, in acatcharound each call, or
with a_arm alone. - one
-
A lambda handed to
scopedornurseryinside a function with a
scopeornurseryof its own used the enclosing one (silent wrong
answer). Infn f() with { scope: Scope },scoped(fn () => work())gave
workthescopeoff, not the onescopedopens, so whatwork
acquired was released whenf's caller's scope ended rather than when
thatscopeddid. The capabilityscopedornurseryhands its lambda
is the lambda's own, and a binding of that label outside the lambda does
not shadow it. A capability the lambda writes by name,scope.defer(..),
is the binding of that name in scope where the lambda is written, as any
name is. -
On the scheduler backend, a finalizer could be skipped (see Fixed). A
program onKHORA_FIBERS=schedulerwhose cleanup was silently skipped
under load runs it. -
A record field is private to the module that declares its type, unless
it is markedpub. Outside that module a private field cannot be read,
assigned, named in a literal or an update, or bound in a pattern, and a
value with any private field can only be built by its own module. A
wrapper's value follows the same rule:type Port = Int;can be built with
Port(n)and opened withmatch p { Port(n) => .. }only in its module,
andtype UserId = pub Int;opens one to everybody. A case's payload is
always public.derive(Decode)is refused on apubtype with a private
field, because it would build one from input wit...
Khora 0.4.0-rc.2
Coming from 0.3.0, read three groups of entries first.
What stops code compiling. A record field is private to its module
unless it is marked pub. Glob imports and a second with or raises
clause are removed. A bare case name in a pattern, a constructor pattern of
the wrong type and two instantiations of one error type in a row are
refused. A Region, a Scope and a Db stay on the fiber that opened
them, and a spawned fiber's error must be Share. Fiber::cancelled is
spelled Fiber::canceled. A handler for Db needs query_each, depth,
savepoint, release and rollback_to. A khora.toml that does not load
stops check, build and the other project commands, and three shapes
0.3.0 warned about are refused.
Wrong answers given with nothing to say so, fixed, so code that ran on
0.3.0 may behave differently: a postgres query could return another
caller's rows; std::db::transaction could lose writes it had answered
Ok; a transaction inside another one committed or rolled back the outer
one; a postgres transaction whose body ignored a failed statement was
answered Ok with nothing committed; the type checker let through a pattern
of the wrong type, a catch that yielded 0 and a raise that no catch
saw; a case name written bare in a pattern matched every value; a catch
arm could read a generic error's field at the wrong type; and a lambda
handed to scoped inside a function with a scope of its own released what
it acquired at the outer scope's end.
Crashes a server operator meets, each of them in 0.3.0: every Router
server died after about 130,000 connections; one client that reset its
connection killed the whole server with SIGPIPE; the otlp exporter
crashed every traced service after about 20,000 spans; a 31 KB request body
of nested brackets, or other long input, ran the stack out and ended the
server; and a fiber on the scheduler backend had an eighth of main's
stack.
Cancellation has its own channel. A canceled fiber stops at its next
cancellation point in every function, whatever the function's raises row,
which changes what SIGTERM, Fiber::join, Fiber::outcome and catch do
in code with no row. On the TechEmpower tests Khora serves more requests a
second than Go on JSON, as many on Fortunes, and fewer on the two database
tests (see Changed).
Breaking
-
A
postgresquery could return another caller's rows (see Fixed). A
program that ran with a receive deadline, or on a network that dropped
reads, may have acted on answers that belonged to other queries. The
connection that caused it is closed instead of reused, and its pool slot
reconnects it (see the pool entry below). -
std::db::transactioncould lose a write it had acknowledged withOk
(see Fixed). Atransactionwhose fiber is canceled before its body
finishes is rolled back, including one canceled whileCOMMITwaited for
its reply, which changes nothing if the commit ran. ADbhandler has a
rule to follow for it; see theDbhandler entry below. -
A
transactioninside atransactioncommitted or rolled back the
enclosing one (see Fixed). An innertransactionis a savepoint: its
writes are committed with the enclosing transaction's or not at all, and
its failure undoes only its own writes. -
A
postgrestransaction whose body ignored a failed statement was
answeredOk(see Fixed). It is answeredDbError::RolledBack. -
A bare name in a pattern that is the name of one of its value's cases is
refused, inmatch,catch,letandfor, at any depth, and for
cases with a payload as well as without.Red => ..over aColorbinds
every color; the message names the pattern to write,Color::Red
(FsError::NotFound(_)for a payload case, and the type's module where
the file never imported it). A bare name that is a type's only case, as
incatch { Stop => () }over a recordStop, matched what the case
would have and is refused as well: write_. The check is against the
value's type, so a case added to a type later makes a binding of the same
name an error rather than a match. A capitalized bare name that is no case
of the value's type is refused as well: a name in a pattern that starts with
a capital letter must be a case. The message reads like an undefined
name and offers the nearest case, "Colorhas no caseGren. Did you
meanColor::Green?", when one is within two edits (or a third of the
name, if that is more); the language server offers it as a quick fix. A
constwritten as a pattern (FAVORITE => .., which binds rather than
compares) is told to use a guard,n if n == FAVORITE. Bind with a
lower-case name. 0.3.0 built all of these with anunused-binding
warning at most, and gave a wrong answer (see Fixed). -
A constructor pattern of one type, matched against a value of another,
is refused, at any depth, inmatch,catch,letand a pattern inside
a tuple or record pattern: "this pattern is aResultcase, and the value
here is aBig". This includes a pattern over a type parameter ("... the
value here is aT"). Where both types were held on the heap, 0.3.0 built
the program and read the field out of the wrong constructor (see Fixed). -
(raise X) catch { X => 7 }is anInt, the type its arms produce.
0.3.0 typed it as the operand's, which never finishes, so it fitted
wherever a value was wanted and yielded 0 there (see Fixed). A use that
fitted only because of that is refused:
let s: String = (raise X) catch { X => 7 }. -
A
raiseof a value whose type is worked out later is charged to the
catches and the row around it, as araiseof a value of known type
is. On 0.3.0 such an error went past everycatch(see Fixed). Programs
this refuses:- the error escapes a closure's
catchand the closure is called without
!: "kcan leave this function, so the call needs!"; - the value turns out to be a second instantiation of an error type the
catchbeside it names: "acatcharm handles one instantiation of a
type, and this operand raises two; catch them with_, or in two
catches". A program of this shape whose other branch was the one that
ran happened to give the right answer, and is refused all the same; - the value's type is never worked out at all: "the type of this raised
value was never worked out"; - the value turns out to be an error type that the closure's row, already
fixed by where the closure is passed (attempt,retry, a parameter
written() -> Int raises Nf), does not carry: "thisraisesendsDn,
and the closure it is in had its error row closed to{ Nf: Nf }, which
does not carry it", naming where the row was fixed and suggesting an
annotation. Another closure calling this one beside aDnof its own
does not count: the row has to carry it.
0.3.0 built these and crashed, or, where the raising branch never ran or
the error happened to reach acatchthat named it, gave the right answer. - the error escapes a closure's
-
An error type appears in a
raisesrow at one instantiation. An arm
that reads a field of a generic error at a type other than the one raised
is refused; on 0.3.0 it read the wrong type (see Fixed). So is anything
that would put two instantiations of one error type in one row, wherever
they meet:- one
catchover(if b { fi()! } else { fs()! }), withfiraising
Gx<Int>andfsraisingGx<String>, when it has aGx::..arm or an
arm that binds the failure (catch { e => .. }), including a_beside
aGx::..arm; - a closure whose body raises both, however it is then used: called,
passed toattemptor a row-polymorphic function, or given to
Fiber::spawn; raises E + Finstantiated withEandFtwo instantiations of one
type;- two different error types that share a name, declared in two modules
(anm::a::Eand anm::b::E), wherever the same routes put them in
one row. An error row is labeled by the type's bare name, so the two
collide as two instantiations do; a named arm there read one type's
field as the other's on 0.3.0. The message says they are two types of
one name.
One arm reads one layout, and the program cannot say which instantiation
arrived. Catch each where it is raised, in acatcharound each call, or
with a_arm alone. - one
-
A lambda handed to
scopedornurseryinside a function with a
scopeornurseryof its own used the enclosing one (silent wrong
answer). Infn f() with { scope: Scope },scoped(fn () => work())gave
workthescopeoff, not the onescopedopens, so whatwork
acquired was released whenf's caller's scope ended rather than when
thatscopeddid. The capabilityscopedornurseryhands its lambda
is the lambda's own, and a binding of that label outside the lambda does
not shadow it. A capability the lambda writes by name,scope.defer(..),
is the binding of that name in scope where the lambda is written, as any
name is. -
On the scheduler backend, a finalizer could be skipped (see Fixed). A
program onKHORA_FIBERS=schedulerwhose cleanup was silently skipped
under load runs it. -
A record field is private to the module that declares its type, unless
it is markedpub. Outside that module a private field cannot be read,
assigned, named in a literal or an update, or bound in a pattern, and a
value with any private field can only be built by its own module. A
wrapper's value follows the same rule:type Port = Int;can be built with
Port(n)and opened withmatch p { Port(n) => .. }only in its module,
andtype UserId = pub Int;opens one to everybody. A case's payload is
always public.derive(Decode)is refused on apubtype with a private
field, because it would build one from input wit...
Khora v0.4.0-rc.1
Coming from 0.3.0, read three groups of entries first.
What stops code compiling. A record field is private to its module
unless it is marked pub. Glob imports and a second with or raises
clause are removed. A bare case name in a pattern, a constructor pattern of
the wrong type and two instantiations of one error type in a row are
refused. A Region, a Scope and a Db stay on the fiber that opened
them, and a spawned fiber's error must be Share. Fiber::cancelled is
spelled Fiber::canceled. A handler for Db needs query_each, depth,
savepoint, release and rollback_to. A khora.toml that does not load
stops check, build and the other project commands, and three shapes
0.3.0 warned about are refused.
Wrong answers given with nothing to say so, fixed, so code that ran on
0.3.0 may behave differently: a postgres query could return another
caller's rows; std::db::transaction could lose writes it had answered
Ok; a transaction inside another one committed or rolled back the outer
one; a postgres transaction whose body ignored a failed statement was
answered Ok with nothing committed; the type checker let through a pattern
of the wrong type, a catch that yielded 0 and a raise that no catch
saw; a case name written bare in a pattern matched every value; a catch
arm could read a generic error's field at the wrong type; and a lambda
handed to scoped inside a function with a scope of its own released what
it acquired at the outer scope's end.
Crashes a server operator meets, each of them in 0.3.0: every Router
server died after about 130,000 connections; one client that reset its
connection killed the whole server with SIGPIPE; the otlp exporter
crashed every traced service after about 20,000 spans; a 31 KB request body
of nested brackets, or other long input, ran the stack out and ended the
server; and a fiber on the scheduler backend had an eighth of main's
stack.
Cancellation has its own channel. A canceled fiber stops at its next
cancellation point in every function, whatever the function's raises row,
which changes what SIGTERM, Fiber::join, Fiber::outcome and catch do
in code with no row. On the TechEmpower tests Khora serves more requests a
second than Go on JSON, as many on Fortunes, and fewer on the two database
tests (see Changed).
Breaking
-
A
postgresquery could return another caller's rows (see Fixed). A
program that ran with a receive deadline, or on a network that dropped
reads, may have acted on answers that belonged to other queries. The
connection that caused it is closed instead of reused, and its pool slot
reconnects it (see the pool entry below). -
std::db::transactioncould lose a write it had acknowledged withOk
(see Fixed). Atransactionwhose fiber is canceled before its body
finishes is rolled back, including one canceled whileCOMMITwaited for
its reply, which changes nothing if the commit ran. ADbhandler has a
rule to follow for it; see theDbhandler entry below. -
A
transactioninside atransactioncommitted or rolled back the
enclosing one (see Fixed). An innertransactionis a savepoint: its
writes are committed with the enclosing transaction's or not at all, and
its failure undoes only its own writes. -
A
postgrestransaction whose body ignored a failed statement was
answeredOk(see Fixed). It is answeredDbError::RolledBack. -
A bare name in a pattern that is the name of one of its value's cases is
refused, inmatch,catch,letandfor, at any depth, and for
cases with a payload as well as without.Red => ..over aColorbinds
every color; the message names the pattern to write,Color::Red
(FsError::NotFound(_)for a payload case, and the type's module where
the file never imported it). A bare name that is a type's only case, as
incatch { Stop => () }over a recordStop, matched what the case
would have and is refused as well: write_. The check is against the
value's type, so a case added to a type later makes a binding of the same
name an error rather than a match. A capitalized bare name that is no case
of the value's type is refused as well: a name in a pattern that starts with
a capital letter must be a case. The message reads like an undefined
name and offers the nearest case, "Colorhas no caseGren. Did you
meanColor::Green?", when one is within two edits (or a third of the
name, if that is more); the language server offers it as a quick fix. A
constwritten as a pattern (FAVORITE => .., which binds rather than
compares) is told to use a guard,n if n == FAVORITE. Bind with a
lower-case name. 0.3.0 built all of these with anunused-binding
warning at most, and gave a wrong answer (see Fixed). -
A constructor pattern of one type, matched against a value of another,
is refused, at any depth, inmatch,catch,letand a pattern inside
a tuple or record pattern: "this pattern is aResultcase, and the value
here is aBig". This includes a pattern over a type parameter ("... the
value here is aT"). Where both types were held on the heap, 0.3.0 built
the program and read the field out of the wrong constructor (see Fixed). -
(raise X) catch { X => 7 }is anInt, the type its arms produce.
0.3.0 typed it as the operand's, which never finishes, so it fitted
wherever a value was wanted and yielded 0 there (see Fixed). A use that
fitted only because of that is refused:
let s: String = (raise X) catch { X => 7 }. -
A
raiseof a value whose type is worked out later is charged to the
catches and the row around it, as araiseof a value of known type
is. On 0.3.0 such an error went past everycatch(see Fixed). Programs
this refuses:- the error escapes a closure's
catchand the closure is called without
!: "kcan leave this function, so the call needs!"; - the value turns out to be a second instantiation of an error type the
catchbeside it names: "acatcharm handles one instantiation of a
type, and this operand raises two; catch them with_, or in two
catches". A program of this shape whose other branch was the one that
ran happened to give the right answer, and is refused all the same; - the value's type is never worked out at all: "the type of this raised
value was never worked out"; - the value turns out to be an error type that the closure's row, already
fixed by where the closure is passed (attempt,retry, a parameter
written() -> Int raises Nf), does not carry: "thisraisesendsDn,
and the closure it is in had its error row closed to{ Nf: Nf }, which
does not carry it", naming where the row was fixed and suggesting an
annotation. Another closure calling this one beside aDnof its own
does not count: the row has to carry it.
0.3.0 built these and crashed, or, where the raising branch never ran or
the error happened to reach acatchthat named it, gave the right answer. - the error escapes a closure's
-
An error type appears in a
raisesrow at one instantiation. An arm
that reads a field of a generic error at a type other than the one raised
is refused; on 0.3.0 it read the wrong type (see Fixed). So is anything
that would put two instantiations of one error type in one row, wherever
they meet:- one
catchover(if b { fi()! } else { fs()! }), withfiraising
Gx<Int>andfsraisingGx<String>, when it has aGx::..arm or an
arm that binds the failure (catch { e => .. }), including a_beside
aGx::..arm; - a closure whose body raises both, however it is then used: called,
passed toattemptor a row-polymorphic function, or given to
Fiber::spawn; raises E + Finstantiated withEandFtwo instantiations of one
type;- two different error types that share a name, declared in two modules
(anm::a::Eand anm::b::E), wherever the same routes put them in
one row. An error row is labeled by the type's bare name, so the two
collide as two instantiations do; a named arm there read one type's
field as the other's on 0.3.0. The message says they are two types of
one name.
One arm reads one layout, and the program cannot say which instantiation
arrived. Catch each where it is raised, in acatcharound each call, or
with a_arm alone. - one
-
A lambda handed to
scopedornurseryinside a function with a
scopeornurseryof its own used the enclosing one (silent wrong
answer). Infn f() with { scope: Scope },scoped(fn () => work())gave
workthescopeoff, not the onescopedopens, so whatwork
acquired was released whenf's caller's scope ended rather than when
thatscopeddid. The capabilityscopedornurseryhands its lambda
is the lambda's own, and a binding of that label outside the lambda does
not shadow it. A capability the lambda writes by name,scope.defer(..),
is the binding of that name in scope where the lambda is written, as any
name is. -
On the scheduler backend, a finalizer could be skipped (see Fixed). A
program onKHORA_FIBERS=schedulerwhose cleanup was silently skipped
under load runs it. -
A record field is private to the module that declares its type, unless
it is markedpub. Outside that module a private field cannot be read,
assigned, named in a literal or an update, or bound in a pattern, and a
value with any private field can only be built by its own module. A
wrapper's value follows the same rule:type Port = Int;can be built with
Port(n)and opened withmatch p { Port(n) => .. }only in its module,
andtype UserId = pub Int;opens one to everybody. A case's payload is
always public.derive(Decode)is refused on apubtype with a private
field, because it would build one from input wit...
Khora for VS Code vscode-v0.3.3
The Khora extension for Visual Studio Code.
Upgrading from 0.3.0 or earlier
The extension's ID is khora.khora. Version 0.3.0 was published as
khora-lang.khora, which VS Code treats as a different extension, so remove
it first or both will run:
code --uninstall-extension khora-lang.khora
Changes in 0.3.3
- A new extension page. The page shown in VS Code and on the
Marketplace describes what the extension does and how to set it up: every
feature, the settings, the commands and troubleshooting. Notes for people
working on the extension itself are inDEVELOPING.mdin the repository.
No change to what the extension does.
Changes in 0.3.2
- The language server starts. 0.3.0 and 0.3.1 launched
khora lsp --stdio, which
every released toolchain refuses with unexpected argument '--stdio', so
the server exited before it answered and nothing but syntax colouring
worked. It launcheskhora lsp.
Changes in 0.3.1
- The publisher is
khora, the name the project owns on the Marketplace. - Install Khora, offered when no
khorais found, opens the installation
page on khoralang.com. In 0.3.0 it opened a domain that does not resolve. - The status bar shows which toolchain answered, and turns yellow when a
project pins a version that is not installed. - Highlighting for character literals (
'a'),rowdeclarations,///doc
comments, string interpolation, and the?,..and comparison operators. - Requires VS Code 1.82 or later.
Install
Download khora-vscode-<version>.vsix below, then:
code --install-extension khora-vscode-<version>.vsix
or in VS Code: Extensions, the ... menu, Install from VSIX.
Then fully quit and reopen VS Code — extensions are scanned at startup, so
reloading the window is not enough. code --list-extensions should list
khora.khora.
It needs the toolchain
The extension is a client. It starts khora lsp, a subcommand of the compiler,
and shows what the compiler says — so it needs khora on your PATH. Install
it from the toolchain releases,
or point khora.server.path at an executable, which is what to do when working
on the compiler itself.
The two are released separately and on purpose. This extension has no compiler
in it, versions on its own, and changes for its own reasons; a fix to hover
rendering should not wait for a language release or drag one along.
What you get
Everything but the syntax highlighting is answered by the compiler, over LSP,
from the same queries khora check runs. There is no second implementation to
drift.
- Errors and warnings as you type, including lints
- Hover types, go to definition, find references, rename
- Completion: methods after
., a type's own items after::, a module's
exports inside an import list - Semantic highlighting the resolver decides — a local told from an import,
which no regex can do - Inlay hints showing what a call costs — the
with { .. }capabilities it
requires and the errors itraises, which is the thing worth reading in a
language with effects - Signature help, quick fixes, document and workspace symbols, run lenses
- Format on save, by
khora fmt, scoped to.khonly
Settings
khora.server.path |
an executable to use instead of khora on PATH |
khora.trace.server |
protocol traffic, into the server's output channel |
Two commands, both under Khora: in the palette — Restart Language Server,
for after rebuilding the compiler, and Show Language Server Output.
Khora for VS Code vscode-v0.3.2
The Khora extension for Visual Studio Code.
Upgrading from 0.3.0 or earlier
The extension's ID is khora.khora. Version 0.3.0 was published as
khora-lang.khora, which VS Code treats as a different extension, so remove
it first or both will run:
code --uninstall-extension khora-lang.khora
Changes in 0.3.2
- The language server starts. 0.3.0 and 0.3.1 launched
khora lsp --stdio, which
every released toolchain refuses with unexpected argument '--stdio', so
the server exited before it answered and nothing but syntax colouring
worked. It launcheskhora lsp.
Changes in 0.3.1
- The publisher is
khora, the name the project owns on the Marketplace. - Install Khora, offered when no
khorais found, opens the installation
page on khoralang.com. In 0.3.0 it opened a domain that does not resolve. - The status bar shows which toolchain answered, and turns yellow when a
project pins a version that is not installed. - Highlighting for character literals (
'a'),rowdeclarations,///doc
comments, string interpolation, and the?,..and comparison operators. - Requires VS Code 1.82 or later.
Install
Download khora-vscode-<version>.vsix below, then:
code --install-extension khora-vscode-<version>.vsix
or in VS Code: Extensions, the ... menu, Install from VSIX.
Then fully quit and reopen VS Code — extensions are scanned at startup, so
reloading the window is not enough. code --list-extensions should list
khora.khora.
It needs the toolchain
The extension is a client. It starts khora lsp, a subcommand of the compiler,
and shows what the compiler says — so it needs khora on your PATH. Install
it from the toolchain releases,
or point khora.server.path at an executable, which is what to do when working
on the compiler itself.
The two are released separately and on purpose. This extension has no compiler
in it, versions on its own, and changes for its own reasons; a fix to hover
rendering should not wait for a language release or drag one along.
What you get
Everything but the syntax highlighting is answered by the compiler, over LSP,
from the same queries khora check runs. There is no second implementation to
drift.
- Errors and warnings as you type, including lints
- Hover types, go to definition, find references, rename
- Completion: methods after
., a type's own items after::, a module's
exports inside an import list - Semantic highlighting the resolver decides — a local told from an import,
which no regex can do - Inlay hints showing what a call costs — the
with { .. }capabilities it
requires and the errors itraises, which is the thing worth reading in a
language with effects - Signature help, quick fixes, document and workspace symbols, run lenses
- Format on save, by
khora fmt, scoped to.khonly
Settings
khora.server.path |
an executable to use instead of khora on PATH |
khora.trace.server |
protocol traffic, into the server's output channel |
Two commands, both under Khora: in the palette — Restart Language Server,
for after rebuilding the compiler, and Show Language Server Output.
Khora for VS Code vscode-v0.3.1
The Khora extension for Visual Studio Code.
Upgrading from 0.3.0
The extension's ID is khora.khora. Version 0.3.0 was published as
khora-lang.khora, which VS Code treats as a different extension, so remove
it first or both will run:
code --uninstall-extension khora-lang.khora
Changes in 0.3.1
- The publisher is
khora, the name the project owns on the Marketplace. - Install Khora, offered when no
khorais found, opens the installation
page on khoralang.com. In 0.3.0 it opened a domain that does not resolve. - The status bar shows which toolchain answered, and turns yellow when a
project pins a version that is not installed. - Highlighting for character literals (
'a'),rowdeclarations,///doc
comments, string interpolation, and the?,..and comparison operators. - Requires VS Code 1.82 or later.
Install
Download khora-vscode-<version>.vsix below, then:
code --install-extension khora-vscode-<version>.vsix
or in VS Code: Extensions, the ... menu, Install from VSIX.
Then fully quit and reopen VS Code — extensions are scanned at startup, so
reloading the window is not enough. code --list-extensions should list
khora.khora.
It needs the toolchain
The extension is a client. It starts khora lsp, a subcommand of the compiler,
and shows what the compiler says — so it needs khora on your PATH. Install
it from the toolchain releases,
or point khora.server.path at an executable, which is what to do when working
on the compiler itself.
The two are released separately and on purpose. This extension has no compiler
in it, versions on its own, and changes for its own reasons; a fix to hover
rendering should not wait for a language release or drag one along.
What you get
Everything but the syntax highlighting is answered by the compiler, over LSP,
from the same queries khora check runs. There is no second implementation to
drift.
- Errors and warnings as you type, including lints
- Hover types, go to definition, find references, rename
- Completion: methods after
., a type's own items after::, a module's
exports inside an import list - Semantic highlighting the resolver decides — a local told from an import,
which no regex can do - Inlay hints showing what a call costs — the
with { .. }capabilities it
requires and the errors itraises, which is the thing worth reading in a
language with effects - Signature help, quick fixes, document and workspace symbols, run lenses
- Format on save, by
khora fmt, scoped to.khonly
Settings
khora.server.path |
an executable to use instead of khora on PATH |
khora.trace.server |
protocol traffic, into the server's output channel |
Two commands, both under Khora: in the palette — Restart Language Server,
for after rebuilding the compiler, and Show Language Server Output.
v0.3.0
Graceful shutdown, a way to ask a cancelled fiber what it ended as, native
libraries on the link line — and another round of strangers building real
programs against the published documentation, in which the tools that tell you
whether your program works turned out to be the ones lying.
The one to read first is signal handling. SIGTERM and SIGINT used to end a
Khora program where it stood; they now cancel it, its finalizers run, and it
exits 130. That is the reason for this release, and it changes the exit status
a supervisor sees, so it is under Breaking as well as Added.
Breaking
-
On Linux and macOS,
SIGTERMandSIGINTunwind the program and exit
130, where they used to kill it outright. A program whosemainhas a
raisesrow now runs its finalizers onsystemctl stop,docker stop, a
Kubernetes rollout orCtrl-C, and ends with exit status 130 rather than
being killed by the signal. Added, below, has what the program observes.Listed as breaking for two reasons. The status changes: a wrapper script, a
health check or a restart policy that matched on "killed by signal 15" or on
exit status 143 sees an ordinary exit 130 instead. And the first signal is
now a request: a program busy in work with no cancellation point — see the
limits under Added — keeps running until that work ends or a second signal
arrives, where it used to die at once. A program whosemainhas no
raisesrow is unaffected: it still dies at the first signal, exactly as
before, with no finalizers. -
Fiber::waitis a cancellation point, and needs a failure channel. A
fiber parked inwaitcould not be stopped before, which would have made
graceful shutdown work for some programs and not others. The waiter can be
cancelled while it is parked even when the fiber it waits on cannot fail, so
khora checkrefuses awaitin a function with noraisesclause and no
enclosingcatch, at the call, with or without a!. Inside a closure that
raises nothing, put acatcharound the call or give the closure a failing
type. A generic function whose only channel is a row variable is refused by
khora buildrather thankhora check; Limitations has the shape.A
waiton a fiber whose body can fail raises that fiber's row, asjoin
always has: writeFiber::wait(f)!in a function that raises it. Give
mainaraisesrow and catch the cancellation where the program can
answer it; that row is also what signal handling needs. -
A test suite that was green may be red after upgrading.
khora test
reported verdicts under the wrong names whentestblocks were spread across
files — Fixed has the details — so a failing test could be reportedok
under another test's name. Three tests in Khora's own standard library suite
had been failing all along and reporting success this way. A test that fails
now was failing before; read it rather than the report you had. -
Listing a directory that exists but cannot be read raises
IoError::Failed, notIoError::NotFound. Code that readsNotFoundas
"absent, so create it" or "absent, so skip it" now takes the other branch on
an unreadable directory. Fixed has the bug. -
A row written in a local type annotation is checked, so
khora check
refuses programs it used to accept. Aletannotation naming a function
type with awithorraisesclause, or a type argument carrying a row —
List<Job<Int, { Bad: Bad }>>— was read as a type that agrees with
anything. Passing such a value to a parameter declaring a different row
compiled clean. It is now refused: at theletwhen the annotation disagrees
with the value, and at the call when the parameter does.Listed as breaking because a program that compiled may not; it was already
wrong, and the annotation it wrote was never being read. Fixed, below, has
the other half: programs this reading accepts that were refused before. -
where,yield,macro,unsafeandunstableare reserved. Khora
has no editions, so a keyword claimed after 1.0 would break every program
that used the word as a name with no way to migrate. These five are held
back now, while it is cheap; none of them means anything yet, and none is
highlighted as a keyword. Using one as a name is an error saying so, rather
than a parse error:error: `yield` is reserved for a future version of Khora and cannot be used as a name. ...Rename the binding, field or function.
-
A dependency contributes only the modules under its own name. A package
namedcsvpublishescsvand everycsv::*; a module beside it with any
other name — acsv_testholding the author's tests, or ahelpersthe
author never meant to publish — is no longer compiled into your build. It
was, with three consequences: a consumer couldimporta library's test
module and use types written for tests,khora testin a consumer ran every
dependency's suite — so somebody else's failing test failed your run — and
khora docpublished a page for each test module in a library's public API.Listed as breaking because a program that imported such a module no longer
compiles, and a library whose public modules are not rooted at its package
name cannot be imported until its author moves them under it. A library that
puts its tests in the same file as its code, askhora new --libdoes, is
imported as before. -
A directory named
buildis skipped when collecting sources, at any
depth, alongsidetargetand.git.khora buildwrites its output to
build/, and a stray.khfile there was compiled and its errors reported
against your program. The Getting started page already saidbuild/was
skipped; the compiler now agrees.Listed as breaking because a package that keeps modules in a directory named
build—src/build/— will find them missing. Rename the directory.
Fixed
-
khora testandkhora benchreported results under the wrong names when
blocks were spread across files. Test keys were numbered per file, so the
nth block of one file and the nth of another shared one, and the lookup that
paired a compiled body with its name ignored which file it came from.The visible effects: a failing test reported
okwith the run exiting 0, a
test that cannot fail reported as failing, totals that described a suite
that did not exist, and aDictinsertion benchmark that appeared to run six
times faster with four times the keys. The measurements themselves were
correct; only the
names on them were wrong, so there was nothing in the output to suggest a
mix-up. Also under Breaking, because a verdict you have been reading may not
say what you thought it said. -
A record returned from a fiber could abort the process. Joining a fiber
whose answer was a record holding a computedStringprinted the right
answer and thenkhora runtime: drop of an object whose refcount is already zero, exit 134, withkhora checkclean. The value's contents were not
counted when it was read out of the fiber, so two owners released them.
20000 spawn-and-join cycles now hold steady in memory, so this is fixed
rather than traded for a leak. -
A path containing a non-ASCII character took the program down. Every
filesystem call checks its path against the permission grant, and that check
cut multi-byte characters in half: oneRésumé.pdfin a directory being
walked ended the program withthis slice cuts a character in half, from
insidestd, uncatchable, naming no path. Any path is now accepted, and a
path outside the grant is still refused. -
A directory that cannot be read was reported as not there. Listing a
locked directory raisedIoError::NotFound, which sends somebody to check a
path that is exactly where they left it instead of at the permission bit.
Files already drew this distinction; directories now draw it too, and an
absent directory still raisesNotFound. Also under Breaking, because code
that branched onNotFoundbehaves differently. -
Cancelling a fiber whose body is a nursery returns. It hung:
Fiber::cancelreturned andFiber::waitnever did, with no message,
because the nursery's children were never told. The cancellation now reaches
them, and cancelling through a nursery costs what cancelling a directly
spawned child does. This is what makes a deadline expressible — see the new
Timeouts and cancellation recipe — and what signal handling is built on. -
Cancelling a fiber that is serving connections stops it.
Fiber::cancel
on a fiber inRouter::listenhung the process for ever with nothing on any
stream, even for an idle listener:mainran to its last line and never
exited. A fiber waiting to accept a connection or to send now stops when it
is cancelled, on both fiber backends. The documentation had described this as
an abort with a named message, which it never was, and recommended
Fiber::detachinstead, which leaves the listener just as stuck; both are
corrected. -
A
letannotation that names a row is read. Annotating a list of
closures with the row they actually raise —
let handlers: List<(Int) -> () raises Boom> = [...]— and then iterating
it failed with an error aboutIterator::nextand impl resolution, three
lines from anything you wrote. The row was being thrown away. It now checks
clean, and aforover a list of row-carrying values is no longer refused.The same annotations were accepted when they named the wrong row, which is
the silently wrong half of this, and is under Breaking. -
A wrapper type is constructed by name wherever it is imported.
pub type Money = Int;is builtMoney(499), as the Types page teaches, but
from any module other than the declaring one that failed with *the type of
this expression was never work...
v0.3.0-rc.1
Graceful shutdown, a way to ask a cancelled fiber what it ended as, native
libraries on the link line — and another round of strangers building real
programs against the published documentation, in which the tools that tell you
whether your program works turned out to be the ones lying.
The one to read first is signal handling. SIGTERM and SIGINT used to end a
Khora program where it stood; they now cancel it, its finalizers run, and it
exits 130. That is the reason for this release, and it changes the exit status
a supervisor sees, so it is under Breaking as well as Added.
Breaking
-
On Linux and macOS,
SIGTERMandSIGINTunwind the program and exit
130, where they used to kill it outright. A program whosemainhas a
raisesrow now runs its finalizers onsystemctl stop,docker stop, a
Kubernetes rollout orCtrl-C, and ends with exit status 130 rather than
being killed by the signal. Added, below, has what the program observes.Listed as breaking for two reasons. The status changes: a wrapper script, a
health check or a restart policy that matched on "killed by signal 15" or on
exit status 143 sees an ordinary exit 130 instead. And the first signal is
now a request: a program busy in work with no cancellation point — see the
limits under Added — keeps running until that work ends or a second signal
arrives, where it used to die at once. A program whosemainhas no
raisesrow is unaffected: it still dies at the first signal, exactly as
before, with no finalizers. -
Fiber::waitis a cancellation point, and needs a failure channel. A
fiber parked inwaitcould not be stopped before, which would have made
graceful shutdown work for some programs and not others. The waiter can be
cancelled while it is parked even when the fiber it waits on cannot fail, so
khora checkrefuses awaitin a function with noraisesclause and no
enclosingcatch, at the call, with or without a!. Inside a closure that
raises nothing, put acatcharound the call or give the closure a failing
type. A generic function whose only channel is a row variable is refused by
khora buildrather thankhora check; Limitations has the shape.A
waiton a fiber whose body can fail raises that fiber's row, asjoin
always has: writeFiber::wait(f)!in a function that raises it. Give
mainaraisesrow and catch the cancellation where the program can
answer it; that row is also what signal handling needs. -
A test suite that was green may be red after upgrading.
khora test
reported verdicts under the wrong names whentestblocks were spread across
files — Fixed has the details — so a failing test could be reportedok
under another test's name. Three tests in Khora's own standard library suite
had been failing all along and reporting success this way. A test that fails
now was failing before; read it rather than the report you had. -
Listing a directory that exists but cannot be read raises
IoError::Failed, notIoError::NotFound. Code that readsNotFoundas
"absent, so create it" or "absent, so skip it" now takes the other branch on
an unreadable directory. Fixed has the bug. -
A row written in a local type annotation is checked, so
khora check
refuses programs it used to accept. Aletannotation naming a function
type with awithorraisesclause, or a type argument carrying a row —
List<Job<Int, { Bad: Bad }>>— was read as a type that agrees with
anything. Passing such a value to a parameter declaring a different row
compiled clean. It is now refused: at theletwhen the annotation disagrees
with the value, and at the call when the parameter does.Listed as breaking because a program that compiled may not; it was already
wrong, and the annotation it wrote was never being read. Fixed, below, has
the other half: programs this reading accepts that were refused before. -
where,yield,macro,unsafeandunstableare reserved. Khora
has no editions, so a keyword claimed after 1.0 would break every program
that used the word as a name with no way to migrate. These five are held
back now, while it is cheap; none of them means anything yet, and none is
highlighted as a keyword. Using one as a name is an error saying so, rather
than a parse error:error: `yield` is reserved for a future version of Khora and cannot be used as a name. ...Rename the binding, field or function.
-
A dependency contributes only the modules under its own name. A package
namedcsvpublishescsvand everycsv::*; a module beside it with any
other name — acsv_testholding the author's tests, or ahelpersthe
author never meant to publish — is no longer compiled into your build. It
was, with three consequences: a consumer couldimporta library's test
module and use types written for tests,khora testin a consumer ran every
dependency's suite — so somebody else's failing test failed your run — and
khora docpublished a page for each test module in a library's public API.Listed as breaking because a program that imported such a module no longer
compiles, and a library whose public modules are not rooted at its package
name cannot be imported until its author moves them under it. A library that
puts its tests in the same file as its code, askhora new --libdoes, is
imported as before. -
A directory named
buildis skipped when collecting sources, at any
depth, alongsidetargetand.git.khora buildwrites its output to
build/, and a stray.khfile there was compiled and its errors reported
against your program. The Getting started page already saidbuild/was
skipped; the compiler now agrees.Listed as breaking because a package that keeps modules in a directory named
build—src/build/— will find them missing. Rename the directory.
Fixed
-
khora testandkhora benchreported results under the wrong names when
blocks were spread across files. Test keys were numbered per file, so the
nth block of one file and the nth of another shared one, and the lookup that
paired a compiled body with its name ignored which file it came from.The visible effects: a failing test reported
okwith the run exiting 0, a
test that cannot fail reported as failing, totals that described a suite
that did not exist, and aDictinsertion benchmark that appeared to run six
times faster with four times the keys. The measurements themselves were
correct; only the
names on them were wrong, so there was nothing in the output to suggest a
mix-up. Also under Breaking, because a verdict you have been reading may not
say what you thought it said. -
A record returned from a fiber could abort the process. Joining a fiber
whose answer was a record holding a computedStringprinted the right
answer and thenkhora runtime: drop of an object whose refcount is already zero, exit 134, withkhora checkclean. The value's contents were not
counted when it was read out of the fiber, so two owners released them.
20000 spawn-and-join cycles now hold steady in memory, so this is fixed
rather than traded for a leak. -
A path containing a non-ASCII character took the program down. Every
filesystem call checks its path against the permission grant, and that check
cut multi-byte characters in half: oneRésumé.pdfin a directory being
walked ended the program withthis slice cuts a character in half, from
insidestd, uncatchable, naming no path. Any path is now accepted, and a
path outside the grant is still refused. -
A directory that cannot be read was reported as not there. Listing a
locked directory raisedIoError::NotFound, which sends somebody to check a
path that is exactly where they left it instead of at the permission bit.
Files already drew this distinction; directories now draw it too, and an
absent directory still raisesNotFound. Also under Breaking, because code
that branched onNotFoundbehaves differently. -
Cancelling a fiber whose body is a nursery returns. It hung:
Fiber::cancelreturned andFiber::waitnever did, with no message,
because the nursery's children were never told. The cancellation now reaches
them, and cancelling through a nursery costs what cancelling a directly
spawned child does. This is what makes a deadline expressible — see the new
Timeouts and cancellation recipe — and what signal handling is built on. -
Cancelling a fiber that is serving connections stops it.
Fiber::cancel
on a fiber inRouter::listenhung the process for ever with nothing on any
stream, even for an idle listener:mainran to its last line and never
exited. A fiber waiting to accept a connection or to send now stops when it
is cancelled, on both fiber backends. The documentation had described this as
an abort with a named message, which it never was, and recommended
Fiber::detachinstead, which leaves the listener just as stuck; both are
corrected. -
A
letannotation that names a row is read. Annotating a list of
closures with the row they actually raise —
let handlers: List<(Int) -> () raises Boom> = [...]— and then iterating
it failed with an error aboutIterator::nextand impl resolution, three
lines from anything you wrote. The row was being thrown away. It now checks
clean, and aforover a list of row-carrying values is no longer refused.The same annotations were accepted when they named the wrong row, which is
the silently wrong half of this, and is under Breaking. -
A wrapper type is constructed by name wherever it is imported.
pub type Money = Int;is builtMoney(499), as the Types page teaches, but
from any module other than the declaring one that failed with *the type of
this expression was never work...
v0.2.0
Editor work, a manifest change, and -- from five rounds of strangers building
real programs against the published documentation -- four runtime failures and
an installer that could not be run as root.
The one to read first is the denial of service in std::json::parse: a service
built the way cookbook/json-api describes could be stopped by a single
request carrying a long string.
Breaking
-
std::fs::fold_linesraisesIoErroron bytes that are not UTF-8, where
it used to trap. The old behaviour wasSIGABRTand exit 134, which no
program could catch;read_texton the same file already raised. A tool that
means "this file is not text" can now say so and choose its own exit status.Listed as breaking because a program written around the trap — a wrapper
script watching for 134, or anything relying on the process dying — sees a
catchable failure and exit 1 instead. -
[toolchain] versionis now required, andeditionis gone. Both halves
of one change: two fields were answering the question which Khora builds
this? and only one of them could.edition = "2026"named a year rather than a compiler. Nothing read it — it
was inert by design, waiting for an editions mechanism that does not exist —
so an unknown value had nothing to go wrong with, which is why it took until
0.1.0for anybody to notice thatedition = "1999"built without a word.
It is removed. A manifest that still carries the line gets a warning naming
what replaced it, and builds.[toolchain] versionnames a compiler that exists and selects the binary that
runs: the build, the tests,khora fmtand the editor's language server all
follow it. Requiring it is what makes "this project builds the same way on
your machine" true by default rather than by convention. A project without one
now stops, with the two lines to add in the message — the table name, the key
and the quoting are not things anybody should have to guess at.Two consequences worth knowing:
- In a workspace the pin belongs at the root. The search walks up from
wherever you are to the nearest manifest that has one, and it no longer
stops at the first manifest it finds — which it did, so a member with no
[toolchain]reported that the project pinned nothing while the root above
it pinned something. - The machine default now only governs directories that are not projects.
Inside a project the pin answers, because there is always a pin.
khora newwrites the pin, and does not write one into a member of a
workspace that already has it. Run it against an existing project and the
message tells you what to add. - In a workspace the pin belongs at the root. The search walks up from
-
A pin may be
latestorlatest.rc. The newest toolchain installed on
this machine, release candidates excluded or included.They are deliberately not reproducible, which is the opposite of what a
pin is for, and they exist anyway: the project that most wants to build
against whatever compiler was installed this morning is the one developing the
compiler. Write a version for anything you want built the same way twice.Both resolve against installed toolchains and never over the network — a
channel that asked GitHub for the newest release would put an HTTP request in
front of everykhorainvocation, including the ones an editor makes on every
keystroke.This needed the version ordering fixed first.
VersionderivedOrd, which
comparespre: Option<String>withNonebelowSome— so0.2.0sorted
below0.2.0-rc.1, and "the newest stable release" would have chosen a
release candidate. It now implements the precedence rules from the
specification, including thatrc.2 < rc.10. -
Two rules moved from the code generator into the type checker, so
khora checknow refuses programs it used to accept. Raising a type no
typedeclaration names —raises String— and an integer literal wider
than anInt. Neither has ever built: the code generator refused both, and
the only thing that changed is when you are told.It is listed here rather than under Fixed because of one workflow: a project
whose CI runskhora checkand notkhora buildwill see a failure it did
not see before. That project was already broken; it just had no way to know.
It is also why this is0.2.0and not0.1.1: before 1.0 the minor is
where a breaking change goes, and a new refusal is breaking to somebody even
when every program it refuses was already wrong.The reason it matters beyond the two rules is that
khora-lsppublishes what
the parser, the checker and the lints say and nothing from lowering — so a
rule enforced during code generation is one no editor can show, at any
point, on any platform.scripts/check-backend-rules.shis a new gate that
makes every one of the 52 remaining backend refusals a decision somebody has
recorded, so a third does not arrive unnoticed.
Fixed
-
std::json::parsecould be killed by one long string, which made it a
remote denial of service. The scanner inside a string literal recursed once
per ordinary character, so the depth of the recursion was the length of the
string: 50 KB was fatal on the main thread and about 11 KB inside a request
fiber, where the stack is smaller. No malformed input was needed, only a long
one — andcookbook/json-apitells the reader to set
Router::holding(1048576), so any service accepting a name, a description or
a base64 field could be stopped by a single unauthenticated request. Inside a
fiber there was no message at all, just a dead process.It is a loop now, and 2 MB parses. Escapes still recurse, once per escape,
which is bounded by how many the document has rather than by its length. -
The stack-exhaustion note said the cause was most likely your own code.
It namedstdas walking "lists and strings with loops", which was true of
everything exceptjson::parse— the one function instdthat could
actually do this. A reader whose service died parsing a request body was sent
to audit their handlers. The note now names both possibilities and neither as
the likely one, because without a backtrace it cannot know which. -
A listener that could not bind said nothing and the process did not stop.
Two halves. The runtime'sa fiber ended with an error nobody was waiting fornow prints when the fiber finishes, not when its handle is released —
a server holds its handle and polls a stop flag, so the one shape every
service is written in was the one the message could not reach. And
Fiber::finishedis new, because nothing could ask whether a spawned fiber
was still running:join,waitand letting the handle go all block. A
supervisor loop can now notice its listener died and exit instead of turning
for ever. -
A service could not be restarted on the same port for about a minute.
listen_onset noSO_REUSEADDR, so a port left inTIME_WAITby the
program's own previous run was refused. First start worked, every restart
inside the window did not, which is the edit-stop-start loop. Fixed on Linux
and macOS — with the BSD constants there, which are not Linux's — and
deliberately not on Windows, where the same option permits binding over a
live socket and the rebind being worked around is not refused anyway. -
khora checkandkhora testpassed wherekhora buildfailed on the
same source. A package with anything undersrc/binbuilt each program
with the package's ownsrc/main.khcorrectly excluded, and its test modules
left in — so asrc/main_test.khimported a module that was no longer in the
compilation. A test module is now excluded too, recognised by containing a
testblock rather than by its file name. -
install.shandkhora toolchain installfailed when run as root, which
is how a container, aDockerfileRUNline and most CI steps install
anything.tarrestores an archive's stored ownership when it believes it is
root, the release archives carry the build machine's uid, and every entry
failed withCannot change ownership to uid 1001. At uid 1000 tar ignores
stored ownership, which is why installing by hand always worked. Both now
pass--no-same-owner. -
An unreadable token is reported where it is.
0xFF,0b1010and1 @ 2
all used to come back asthis `{` is never closed, naming a brace on the
line above — an underline your editor drew on code that was correct. Numeric
bases Khora does not have are now named as such, and say what it does have:
decimal digits, with_free to separate them. -
An unreachable
matcharm names its cause.Red => 1where
Colour::Redwas meant reported the next arm — the one written correctly —
as unreachable. A bare name in a pattern is a binding, so it had matched
every colour. The message now says so and gives the line to write. -
A declaration keyword from another language is redirected.
struct,
class,enum,interface,func,var,namespaceandasyncname the
Khora spelling instead of expected a declaration.enumshows the variant
syntax, because being told only "writetype" leaves you looking for a
separate word for cases;asyncis told the distinction does not exist here
rather than that it is a rename.
Changed
-
A span knows what it is inside, and
std::logcorrelates by itself.
std::trace::currentis the span the running fiber is in:aroundinstalls
one for the duration of a body and restores the enclosing one on every path
out, a handler'sstarttakes the trace id, the sampling decision and the
parent from it, and a fiber inherits its spawner's span when it is created,
so work that fans out stays one trace. Until nowSpan::parentwas0
everywhere and a nestedaroundbegan a second trace.**This changes what a
Log::jsonline...
v0.1.0
v0.1.0
The first release with a plain version number. 241 commits since
v0.1.0-rc.3, and the reason it took three candidates to get here is that
each one was scored against docs/release-readiness.md item by item and each
scoring found things the previous one had asserted rather than checked.
This is 0.1.0, and the number is the promise. The language may break
before 1.0, and this release breaks things rc.3 accepted. What it will not
do is break them quietly: CHANGELOG.md lists every one, and anything that
used to produce a silently wrong answer is listed as breaking as well as
fixed, because code written around it behaves differently now.
Install
curl -fsSL https://raw.githubusercontent.com/codyspate/khoralang/main/install.sh | shirm https://raw.githubusercontent.com/codyspate/khoralang/main/install.ps1 | iexThree targets: x86_64-unknown-linux-gnu, x86_64-pc-windows-msvc and
aarch64-apple-darwin. They are exactly what the release workflow builds,
packages, unpacks somewhere else and compiles a program with before anything
is published. Linux arm64, cross-compilation, musl and WebAssembly are named
as out of scope with the reason at https://khoralang.com/docs/next/deployment/supported-targets/,
because an unchecked platform in a supported table is worse than an absent
one.
What changed since rc.3
The full list is in CHANGELOG.md. The four things a program
is most likely to notice:
std::schemareplacesstd::json's decoding, and a type is its own
schema.derive(Decode, Encode)on a record gives you the same reader for
a request body, a database row, an environment block and an LLM's answer,
and it reports a bad value as[2].amount should be a whole numberrather
than dropping it.FromJson/ToJson,std::ai::Extractandstd::config's
eight accessor functions are all deleted in favour of it.khora docno longer defaults to this repository's layout. It defaulted
tostdand towebsite/content/docs/stdlib/api, resolved against wherever
you happened to be standing, and its stale sweep deleted markdown files it
had not written. It is package-relative now and refuses outside a package.Rowcarries its column names, so a query's answer reads through the
same schema as everything else.std::log. Khora had no way to write to standard error at all, which
two of the people who tried building command-line tools with it reported
independently: a program's diagnostics and its answer shared one stream, and
the first> out.txtanybody typed swallowed the diagnostics. Logging is a
capability, emits one JSON object per line, and takes the clock as a value
so a log line can be asserted against.
Also here: an epoll backend on Linux, which is where the portable poll
reactor had a scaling ceiling worth removing (macOS and Windows still run
the portable one — kqueue is the same shape and unwritten, IOCP is a
different interface rather than a backend swap); an OTLP trace exporter in
packages/otlp; TLS read deadlines; and a language server that does the
things people expect one to do — completion that writes the import, code
actions that fill in a missing handler or a missing record field, extract
variable and extract function.
What is not done
docs/release-readiness.md scores 200 of 222. The twenty-two open items are
each written out there with what specifically is left; the ones worth knowing
before you build on this:
- macOS has not had the scheduler soak run against it. Linux and Windows
have. Nothing here can run macOS, which is also why there is nokqueue
backend. - A
bounded_nursery(n)admitsn + 1children, becauseFiber::spawn
starts the child beforeadoptblocks. Subtract one where the limit is a
real resource. - The two fiber backends are distinguishable by a program, which the
concurrency reference says twice they are not. Under
KHORA_FIBERS=schedulera cancelled fiber'ssleepreturns early; under
the default thread backend it runs to completion. - Cancellation lands only where a fiber checks for it — a
!, or a loop
back-edge. A longsleepis not a cancellation point. - A type name in
stdcan collide with one of your own and crash the
program at run time rather than failing to compile. Erratum 79. This is
why the logging level type is calledSeverity. - Thread-affine foreign libraries are untested against fiber migration.
https://khoralang.com/docs/next/limitations/ carries these and the rest, and
docs/errata.md is the list of things known to be wrong.
Documentation
https://khoralang.com/docs/ now serves next — a version, rather than the whole
site — and every page says so. A section per stable major gets cut when v1
ships; before then there is one tree, because there is one compiler anybody
can install and the language may still change under it.