Releases: emerge-lang/compiler
Release list
0.7.0 - Multi-Symbol Imports
Adds support for importing multiple elements/symbols in one import statement:
import emerge.std.io.{IOException, PrintStream}
It also adds a couple other improvements around imports:
- if the same element/symbol is imported multiple times, you get an
INFOdiagnostic - if multiple elements/symbols from different packages with identical simple name are imported, you get an
ERRORdiagnostic - if a type reference is ambiguous due to wildcard imports, you get an
ERRORdiagnostic (before it would just silently resolve to the first type imported) - if you declare two global variables with identical names in the same package (and they both have visibility beyond their file), then you get an
ERRORdiagnostic - the
ERRORdiagnostic around circular variable initialization is now much clearer
Also, some refactorings:
BoundBaseType.baseReferencegot replaced withgetBoundReferenceAssertNoTypeParameters(Span)andgetBoundReferenceWithNoneOrWildcardTypeArguments(Span). This makes a footgun more obvious and also makes sure these type references have a proper Span wherever possible- type inference in
BoundVariablewas refactored; the data and logic flow is now much more obvious from the code
0.6.0 - foreach, iterator protocol and generic supertypes
This release adds multiple bigger features that build on top of each other:
- wildcard type arguments
- generic/parametric supertypes
- iterator protocol
- foreach syntax
Generic/Parameters Supertypes
It is now possible to inherit from interfaces that have type parameters:
interface Iterable<T> {}
class List<T> : Iterable<T> {}
This might seem like a simlpe change on the surface, but it reached quite deep and enables a ton of possibilities.
A concept for containers and iteration
This release also contains decision for how emerge handles containers and iteration. I decided to go with the same core principles as the D language, adapted to fit emerges idioms.
The key types in emerge are: emerge.core.range.Iterable, emerge.core.range.InputRange<T> and all its subtypes.
Foreach
With a concept for iteration in place it is now also possible to build a foreach syntax. That has now been added to the language:
numbers = [1, 1, 2, 3, 5, 8, 13, 21]
foreach n in numbers {
StandardOut.put(n.toString())
StandardOut.putEndOfLine()
}
Foreach is basically syntax sugar, but the important bit is that it defines an iterator protocol, similar to how JavaScript does it, which revolves all around emerge.core.Iterable and emerge.core.InputRange. In the above example, the foreach desugars to this:
_hiddenRange: mut _ = numbers.asRange() // part one of the iterator protocol: Iterable.asRange
do {
n = try {
_hiddenRange.front()
} catch e {
// part two of the iterator protocol: InputRange.front(), plus using the exception to signal end-of-data
if e is EmptyRangeException {
break
} else {
throw e
}
}
StandardOut.put(n.toString())
StandardOut.putEndOfLine()
_hiddenRange.popFront() // part three of the iterator protocol: advancing to the next element
} while true
Wildcard type arguments
It is now also possible to specify * as the argument to a type parameter. The compiler will then figure out the bound of that parameter
and the * is treated as out Bound:
// before
val someIterable: Iterable<out Any>
// after, more concise and easier to refactor
val someIterable: Iterable<*>
Other improvements
As usual, this release also contains bug fixes, improves the clarity of various diagnostic messages and refactorings. I want to point out one of these: when you import a non-existing type, you'll only get an error for that import instead of a gazillion errors for all the times you've used it:
import foo.bar.DoesNotExist
fn frobnicate(p: DoesNotExist, x: DoesNotExist) -> DoesNotExist {
return if p.foo == x.foo p else x
}
Before, you would get 4 errors. One for the import and 3 for the usages. Now its only one.
0.5.0 - Support for the decorator pattern / wrappers
With this release, it becomes possible to have the mutability of a decorator/wrapper depend on the mutability of the object it wraps. If you decorate a const object, the decorator is automatically const, and likewise for mut.
To make use of this, declare the member variable that holds the decorated/wrapped object as decorates:
interface NumberHolder {
get fn n(self) -> U32
set fn n(self: mut _, v: U32)
}
class SimpleNumberHolder : NumberHolder {
private var _n: U32 = init
override get fn n(self) = self._n
override set fn n(self: mut _, v: U32) {
set self._n = v
}
}
class MultiplyBy4NumberHolder : NumberHolder {
decorates private delegate: NumberHolder = init
override get fn n(self) -> U32 {
// here, self.delegate is a read NumberHolder, because the self reference is read, too
return self.delegate.n * 4
}
override set fn n(self: mut _, v: U32) {
if v.rem(4) != 0 {
panic("must be a multiple of 4!")
}
// however, here self.delegate is mut because the decorator reference (self) is mut
set self.delegate.n = v / 4
}
}
fn example1() {
var simple = SimpleNumberHolder(1)
decorated: const MultiplyBy4NumberHolder(simple) // error: simple is mutable, the decorator is also mutable, not assignable to const
}
fn example2() {
var mutDecorated: mut _ = MultiplyBy4NumberHolder(SimpleNumberHolder(1)) // works; exclusive nested object can be mut
constDecorated: const _ = MultiplyBy4NumberHolder(SimpleNumberHolder(2)) // works, too: exclusive nested object can be const
}
This can not yet be combined with secondary constructors effectively, though.
0.4.2 - Mutability of type parameters is considered more stricly for subtype semantics
The mutability of type parameters was not considered as strictly as necessary. Specifically, this surfaces when overriding a function from an interface in a way that at least breaks LSP, or circumvents the mutability restrictions entirely:
// simple interface with a function that mutates the object
interface Parent {
fn mutate(self: mut _)
}
// this concrete type implements the interface, but it also narrows down the type of the
// receiver parameter too much.
class Child<T> : Parent {
override fn mutate(self: mut _<mut T>)
}
When this override is allowed and the new rules for the overriding function are enforced, the LSP is broken because mutate can no longer be called on a mut Child<read Any> reference (so mut Child<...> is no longer guranteed to be a subtype of mut Parent)
If it is allowed and not enforced, the LSP is okay, but now the read Any can be turned into a mut Any, breaking the mutability guarantees of the language.
This is now rejected by the compiler:
(ERROR) Cannot narrow type of overridden parameter self; T cannot be proven to be a subtype of mut T
|
6 | interface Parent {
7 | fn mutate(self: mut _)
| ~👆~~ overridden function establishes the type T
8 | }
...
12 | class Child<T> : Parent {
13 | override fn mutate(self: mut _<mut T>) {}
| 👆 overriding function requires are more specific type
14 | }
|
0.4.1 - Check Simultaneous Borrows
This release fixes a bug where the same object could have a const and a mut reference pointing to it at the same time:
class C {}
fn test() {
var c: exclusive C = C()
trigger(c, c)
}
fn trigger(borrow a: mut C, borrow b: const C) {}
this would previously pass, but is now rejected:
(ERROR) Cannot borrow `c` as const here, because it is already borrowed as mut
|
36 | var c: exclusive C = C()
| 👇 borrowed as mut here
37 | trigger(c, c)
| 👆 attempting to borrow as const while a mut borrow is still active
38 | }
|
0.4.0 - Virtual member variables
This release adds special kinds of member functions that can be used by the member-variable syntax (also called getters and setters):
class Name {
var value: String
// defines a getter for `length`
get fn length(self) = self.value.length
}
fn test1() {
name = Name("Hello!")
nChars = name.length // syntax sugar for .length()
}
// likewise, setters
class EvenIntBox {
private var _value: S32 = 0
set fn value(self: mut _, v: S32) {
if v.rem(2) != 0 {
panic("cannot set to an un-even number")
}
set self._value = v
}
}
fn test2() {
var evenBox = EvenIntBox()
set evenBox.value = 2 // syntax sugar for .value(2); runs fine
set evenBox.value = 3 // panics
}
0.3.2 - Purity is enforced correctly
This version contains a rework of the purity checker. Now, loopholes like these are fixed and will be rejected by the compiler:
class Box {
var n: S32 = 0
}
var globalBox = Box()
read fn test() {
var localBox = globalBox // this in itself is just a reading action and was previously accepted
set localBox.n = 5
}
// also similar tricks, like
read fn test2() -> mut Box = globalBox
Since 0.3.2, this code would be rejected with errors:
(ERROR) creating a mut reference to `globalBox` violates the purity of readonly function `test`
|
12 | read fn test() {
| ~~~👇~~~~ `globalBox` is used with a mut type here
13 | var localBox = globalBox // this in itself is just a reading action and was previously accepted
| ~~~~~👆~~~~~ mut reference is created here
14 | set localBox.n = 5
|
(ERROR) returning `globalBox` violates the purity of readonly function `test2`
|
16 | // also similar tricks, like
| ~~~👇~~~~ `globalBox` is used with a mut type here
17 | read fn test2() -> mut Box = globalBox
| 👆 mut reference is created here
18 |
|
0.3.1 - Stricter purity
This release improves the strictness of the pure and read function modifiers:
class Box {
var x = 3
}
class OuterBox {
var b = Box()
}
fn changeBox(box: mut Box) {
set box.x = 4
}
globalOuterBox: mut _ = OuterBox()
read fn bug1() {
// no longer allowed by the compiler
changeBox(globalBox.b)
}
read fn bug2() {
// no longer allowed by the compiler
set globalBox.b.x = 4
}
0.3.0 - Mixins
This feature relase brings mixins:
interface I {
fn foo(self) -> String
}
class Trait : I {
override fn foo(self) = "hello!"
}
class Concrete : I {
constructor {
// implements I on Concrete by delegating to Trait()
mixin Trait()
}
}
mut fn main() {
StandardOut.put(Concrete().foo())
}
// prints "hello!"
You can see them in action in the stblib for emerge.core.Throwable
It also fixes two bugs:
pureandreadfunctions can no longer modify global variables (was possible if they were declared asval global: mut _ = ...)- classes can now declare member variables that have the same name as a global variable visible in the same file. Previously, the compiler would crash when that happens.
0.2.0
v0.2.0 [RELEASE] Update windows archive reference in bazel/rules_emerge