Skip to main content

2025-02-10

· 3 min read

Language Updates

New is Expression

  1. The syntax for this expression is expr is pat. The expression is a Bool type and evaluates to true when expr matches the pattern pat. For example:
fn use_is_expr(x: Int?) -> Unit {
if x is Some(i) && i >= 10 { ... }
}
  1. The pattern can introduce new binders, which can be used in the following cases:
  • In e1 && e2, if e1 is an is expression, the binders introduced by the pattern can be used in e2.
  • In if e1 && e2 && ... { if_branch } else { ... }, binders introduced by is expressions in the &&-chained conditions like e1 & e2 can be used in the if_branch.

String Construction and Pattern Matching

  1. New support for constructing strings using array spread syntax, for example:
fn string_spread() -> Unit {
let s = "hello🤣😂😍"
let sv = s[1:6]
let str : String = ['x', ..s, ..sv, '😭']
println(str) // xhello🤣😂😍ello🤣😭
}

In an array spread, individual elements are Char values. You can use .. to insert a String or a @string.View segment. This syntax is equivalent to using StringBuilder to construct a string.

  1. Support for pattern matching on strings using array patterns, which can be mixed with array literal patterns, for example:
fn match_str(s: String) -> Unit {
match s {
"hello" => ... // string literal pattern
[ 'a' ..= 'z', .. ] => ... // array pattern
[ .., '😭' ] => ... // array pattern with unicode
_ => ...
}
}

New Compiler Warnings

  • The compiler now warns about unused guard statements and missing cases in guard let ... else ....
fn main {
guard let (a, b) = (1, 2)
^^^^^^^^^ ----- useless guard let
println(a + b)
}

moonfmt Fixes

  • Fixed formatting errors related to async code in moonfmt.
  • Adjusted insertion rules for ///| markers.

Package Updates

  • moonbitlang/x/sys now supports the native backend and fixes inconsistencies across different operating systems.

  • The fs package in moonbitlang/x has been updated with improved error handling.

  • String-related operations are being reorganized. The string package will provide more Unicode-safe APIs while deprecating some APIs that expose UTF-16 implementation details. During this transition, string methods may become unstable. It is recommended to use iter methods or pattern matching to access string elements.

  • Refactored ArrayView/StringView/BytesView types by moving them from the @builtin package to their respective type-related packages. Their names have been updated accordingly to @array.View/@string.View/@bytes.View.

IDE Updates

  • Added code action support for filling in missing cases in pattern matching.

  • Enabled inline autocompletion for all cases in empty pattern matches.

  • Fixed a bug in trait method "Go to Reference".

  • Fixed missing autocompletion for variables introduced in guard let ... else ... and improved pattern completion in else branches.

Build System Updates

  • Fixed a bug in moon test where panic tests were being skipped on the native backend.

Documentation Updates

2025-01-13

· 3 min read

Language Updates

  • Experimental Async Support

    Experimental support for asynchronous programming has been added. You can declare asynchronous functions using async fn ... and call them with f!!(...). MoonBit also provides primitives for interrupting control flow. For details, see docs/async-experimental.

    Currently, the async standard library and event loop are under development. Using the JavaScript backend's event loop and Promise API is easier for asynchronous programming. As this feature is experimental, breaking changes may occur based on feedback. We welcome and appreciate your testing and feedback.

  • Upcoming Changes to Method Semantics

    Later this week, we will make major changes to simplify method-related rules. Currently:

    • Methods can be declared as fn f(self: T, ..) or fn T::f(..).

    • Methods with Self as the first parameter can be called with xx.f(..).

    • If unambiguous, methods can also be called with f(..).

    However, the last rule lacks user control and consistency between call syntax (f(..)) and declaration syntax (T::f(..)). The new design will:

    • Allow fn f(self: T, ..) to define methods callable with xx.f(..) or f(..). These methods share the same namespace as regular functions and cannot have duplicate names.

    • Allow fn T::f(..) to define methods callable with xx.f(..) or T::f(..). These methods cannot be called as regular functions (f(..)).

    Intuition: All fn f(..) definitions are regular functions, while fn T::f(..) definitions are placed in a small namespace associated with T.

    For methods like new (without Self as the first parameter), use fn new(..) to enable direct calls as new(..). Library authors are encouraged to:

    • Use fn f(..) for unambiguous functions and make the first parameter self for xx.f(..) calls.

    • Use fn T::f(..) for potentially ambiguous functions to place them in a scoped namespace.

  • Enhanced Alerts

    • Trigger alerts on type usage.

    • Trigger alerts on specific constructor usage.

  • Improvements to ArrayView/BytesView/StringView:

  1. Support for negative indices in view operations, e.g.:
let arr = [1, 2, 3, 4, 5]
let arr_view = arr[-4:-1]
println(arr_view) // [2, 3, 4]
  1. Support for pattern matching in BytesView with byte literals, e.g.:
fn f(bs: BytesView) -> Option[(Byte, BytesView)] {
match bs {
[b'a'..=b'z' as b, ..bs] => Some((b, bs)),
_ => None
}
}
let s = b"hello"[:]
let r = f(s)
println(r) // Some((b'\x68', b"\x65\x6c\x6c\x6f"))
  1. The as keyword in array patterns is now optional. [a, ..rest, b] is valid, and the formatter will automatically omit as from [a, ..as rest, b].

IDE Updates

  • Added a command to toggle multi-line strings.
  • Added more detailed information to workspace symbols, making it easier to search for specific functions and types.

Build System Updates

  • Fixed a bug in doc tests affecting multi-line string test results.

Documentation Updates

  • MoonBit Tour now supports debug codelens with value tracking enabled by default.

  • Launch Moonbit Online Judge, a platform for users to learn MoonBit by solving problems.

oj.png

Standard Library Updates

  • Updated the behavior of Int64 to JSON conversions. Precision is no longer lost through Double conversion; values are now preserved as strings.

2024-12-30

· 4 min read

Language Update

  • Added labeled loop syntax, which allows you to jump directly to a specified layer in a multi-layer loop, using ~ as the suffix for the label.
fn f[A](xs : ArrayView[A], ys : Iter[Int]) -> @immut/list.T[(Int, A)] {
l1~: loop 0, xs, @immut/list.Nil {
_, [], acc => acc
i, [x, .. as rest], acc =>
for j in ys {
if j == i {
continue l1~ i + 1, rest, @immut/list.Cons((j, x), acc)
}
if j + i == 7 {
break l1~ acc
}
} else {
continue i - 1, rest, acc
}
}
}
  • New Discard Argument, function arguments named with a single underscore will be discarded, multiple arguments can be discarded within the same function.
fn positional(a : Int, b : Int) -> Int {
a + b
}

fn discard_positional(_: Int, _: Int) -> Int {
1
}
  • The semantics of pub have officially changed from being fully public to read-only, and the pub(readonly) syntax has been deprecated. To declare a fully public type/struct/enum, you should now use pub(all). To declare a fully public (externally implementable) trait, use pub(open). This change was previously announced via a warning, and if you already followed the warning and updated pub to pub(all) or pub(open), you only need to replace pub(readonly) with pub to complete the migration. The moon fmt tool can automatically convert pub(readonly) to pub.

  • In the future, the behavior where a trait will fallback to method implementation if no explicit implementation is found may be removed. We encourage new code to use the explicit impl Trait for Type syntax instead of relying on methods for trait implementation. In cases of no ambiguity, you can still use dot syntax (impl Trait for Type with method(...)), so adopting explicit impl syntax won't sacrifice convenience.

  • The old prefix syntax for labeled parameters has been removed.

  • The old syntax for accessing newtype contents using .0 has been removed. Additionally, if a newtype contains a tuple, index access like .0, .1, etc., will now automatically forward to the tuple inside the newtype. For example:

type Tuple (Int, String)

fn main {
let t = (4, "2")
println(t.0) // 4
println(t.1) // 2
}
  • Parameters have been added to derive(FromJson) and derive(ToJson) to control the specific behavior and data layout of type serialization and deserialization. The detailed changes can be found in Docs > Language > Deriving.

    You can now rename fields and adjust the serialization format of enums. The specific behavior of JSON serialization and deserialization may be optimized and changed in the future.

enum UntaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(untagged)), Show)
// { "0": 123 }, { "0": "str" }

enum InternallyTaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(tag="t")), Show)
// { "t": "C1", "0": 123 }

enum ExternallyTaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(ext_tagged)), Show)
// { "C1": { "0": 123 } }

enum AdjacentlyTaggedEnum {
C1(Int)
C2(String)
} derive(ToJson(repr(tag="t", contents="c")), Show)
// { "t": "C1", "c": { "0": 123 } }

struct FieldRenameAllCamel {
my_field : Int
} derive(ToJson(rename_all="camelCase", repr(tag="t")), Show)
// { "t": "fieldRenameAllCamel", "myField": 42 }

struct FieldRenameAllScreamingSnake {
my_field : Int
} derive(ToJson(rename_all="SCREAMING_SNAKE_CASE", repr(tag="t")), Show)
// { "t": "FIELD_RENAME_ALL_SCREAMING_SNAKE", "MY_FIELD": 42 }

IDE Update

  • IDE now supports gotodef/gotoref/rename for loop label.

  • Document symbols are now displayed in a hierarchical way in the IDE, as follows. layer.png

  • Fixed bug in white box testing regarding type renaming.

  • Added IDE support for automatically omitting parameter inlay hints in case of repetition.

  • IDE adds value tracing. Clicking on "Trace" codelen above the main function will enable it, and clicking again will disable it.

    • For variables inside a loop, only the latest value and the hit count are shown. trace.png

Build System Update

  • moon adds a --no-strip parameter to preserve symbol information in release build mode.

Document Update

  • Fixed an issue where MoonBit Tour's theme was not maintained when switching pages.

  • Added range, range pattern and newtype sections to MoonBit Tour.

2024-12-16

· 3 min read

Language Update

  • New Trait object syntax: &Trait

Trait object syntax has been changed from writing Trait directly to &Trait (the old syntax has been deprecated). We made this change to separate trait object type and the trait itself syntactically and avoid confusion. All places that mention trait objects, including type annotation, defiing method on trait objects (fn &Trait::method(...)) and trait object creation (... as &Trait) should be modified to adapt to this change.

  • New Local Types Language Feature

It’s now possible to declare types (structs, enums, newtypes) that are only visible within the scope of the current top-level function. You can also use derive to add new methods to these local types. For example:

fn toplevel[T: Show](x: T) -> Unit {
enum LocalEnum {
A(T)
B(Int)
} derive(Show)
struct LocalStruct {
a: (String, T)
} derive(Show)
type LocalNewtype T derive(Show)
...
}

Note that local types can use the generic parameters of the current top-level function but cannot introduce additional generics of their own. Local types can use derive to generate associated methods, but they cannot define other new methods. Also, declaring an error type within a local type is not yet supported.

IDE Update

  • Fixed some LSP related bugs.

    • Fixed the bug that when hovering, the type of the payload and the type of the error type are linked together.
    • Fixed a bug where LSP would not serve a single file after adding it to a module (creating moon.pkg.json in the same level folder) and then removing it (deleting moon.pkg.json).
    • Fixed test-import-all configurations that were broken sometimes.
    • Fix a bug in LSP's strange inlay hint.
  • Enable block-line option for LSP formatter.

  • LSP supports warn-list configuration.

  • Optimise the debug experience in web IDE. When user opens devtools and clicks debug, it will directly stop at the main function.

debug.gif

  • Allow test wrapping in doctest, support for updating inspect and panic test.
/// ```
/// test "panic test xxx" {
/// panic()
/// }
/// ```
  • MoonBit AI Feature

The AI now supports model switching, interruption, and retrying while generating.

ai.gif

Build System Update

  • moon run runs a test with the support of passing --stack-size directly to adjust the v8 stack size.

  • Breaking Change

During black-box testing, the public definitions from the tested package will be automatically imported. For example, when testing the @json package, functions or types from @json can be used without prefixing @json. To disable this behavior, you must specify "test-import-all": false in the moon.pkg.json file.

Document Update

  • MoonBit Tour

The tutorials on Web IDE have been migrated to the new tour, with changes and additions made accordingly. The code repo is here.

2024-12-02

· 3 min read

Language Updates

  • Added support for range patterns to match a range of integer or character values in pattern matching.

The syntax for range patterns is a..<b (excluding the upper bound b) or a..=b (including the upper bound b). Bounds can be literals, constants declared with const, or _, which indicates no constraint on that side:

const Zero = 0
fn sign(x : Int) -> Int {
match x {
_..<Zero => -1
Zero => 0
Zero..<_ => 1
}
}

fn classify_char(c : Char) -> String {
match c {
'a'..='z' => "lowercase"
'A'..='Z' => "uppercase"
'0'..='9' => "digit"
_ => "other"
}
}
  • Support calling trait with x.f(...)
trait I {
f(Self) -> Unit
}

type MyType Int
impl I for MyType with f(self) { println(self._) }

fn main {
let my : MyType = 42
my.f()// Output 42
}

If x has type T, the x.f(...) syntax resolves as follows:

  1. If T defines a method f, call T::f.
  2. If a trait does impl SomeTrait for T with f in the same package as T, call SomeTrait::f. If multiple f are found, the compiler reports an ambiguity error.
  3. If neither of the above applies, the compiler searches for impl SomeTrait for T with f within the current package. Notice that this rule only work in current package. If T is defined outside the current package, local implementation for T cannot be called with dot syntax outside.

These rules enhance the flexibility of MoonBit’s dot syntax while maintaining semantic clarity and strong refactor safety. Resolution of x.f(...) only involves the package defining T and the current package, avoiding issues caused by newly introduced dependencies.

  • Added support for trait alias

No new syntax is introduced. Trait aliases use the same syntax as type aliases:

typealias MyShow = Show
impl MyShow for MyType with ...

IDE Updates

  • Web IDE now supports updating inspect tests. try-inspect.gif

  • Generated code can be revised in MoonBit AI before inserting, and the revised code is automatically checked for syntax and type correctness. ai-modify.gif

  • Fixed syntax highlighting issues for Markdown code blocks in documentation comments.

Build System Updates

  • moon check now supports passing a warn list.

  • moon test can run tests embedded in project documentation.

    • Usage: moon test --doc runs all tests within documentation comments of the current project.
    • Note: Tests must be enclosed between lines with ```. For example: moon-test.png
  • Fixed an issue with local functions and return expressions in moon fmt.

  • Fixed an issue in moon fmt which comments near array indexing syntax are misplaced during formatting.

  • moon fmt enabled -block-style by default.

Docs Update

2024-11-18

· 4 min read

Language Updates

  • Trait now has abstract and pub(readonly) visibility.
  1. An abstract/readonly trait behaves the same as a fully public trait within the current package.
  2. Outside the current package, you can't call methods in an abstract trait directly, and you can't write new implementations of an abstract trait.
  3. A pub(readonly) trait cannot be implemented outside the current package, but methods can be called directly.
  4. The default visibility for a trait has been changed from private to abstract. To explicitly define a private trait, use the priv trait keyword.
  5. When implementing an abstract or readonly trait, at least one method must be implemented using the impl Trait for Type syntax to allow external usage of that implementation.

Example of abstract trait:

trait Number {
add(Self, Self) -> Self
}

pub fn add[N : Number](x : N, y : N) -> N {
Number::add(x, y)
}

impl Number for Int with add(x, y) { x + y }
impl Number for Double with add(x, y) { x + y}

Using the moon info command to generate a .mbti interface file, the exported interface is as follows:

trait Number
impl Number for Int
impl Number for Double

fn add[N : Number](N, N) -> N

Externally, the add function can only be called for Int and Double. The compiler ensures that the Number trait can only be implemented for Int and Double.

  • Visibility Adjustments for Types and Traits

To encourage the use of readonly types/traits to provide more robust APIs, we plan to change the default visibility for type and trait definitions to readonly in the future.

To declare fully public visibility:

  1. Types: Use pub(all) struct/enum/type.
  2. Traits: Use pub(open) trait to allow external implementations.

Currently:

  1. pub still means fully public visibility, but the compiler will issue a warning recommending migration to pub(all) or pub(open).
  2. The moon fmt formatter will automatically migrate existing pub to pub(all) or pub(open).
  • New Private Fields in struct Types:

Public structs can now have private fields by adding the priv keyword before specific fields.

  1. External Visibility: Private fields are completely hidden from the outside world. They cannot be read or modified externally.
  2. If a struct contains private fields, it cannot be directly constructed using literals from outside the module. ****However, the struct update syntax ({ ..old_struct, new_field: ... }) can be used to update public fields while maintaining the private field's state.
  • New suffix-style label argument syntax

The new syntax is equivalent to moving ~ after the identifier, and the ~ can be omitted in the case of forwarded options of the form ~label?

enum Foo {
Bar(label~ : Int, Bool)
}
fn f(a~ : Int, b~ : Int = 0, c? : Int) -> Unit {...}
fn main {
f(a=1, b=2, c=3)
f(a=1, c?=Some(3))// b Default values are declared and can be omitted. c Forwarding option Syntax as usual
let a = 1
let c = Some(3)
f(a~, c?) // Like at the declaration, punning takes the form of a suffix
}

The old syntax is deprecated:

enum Foo {
//deprecated
Bar(~label : Int, Bool)
}
//deprecated
fn f(~a : Int, ~b : Int = 0, ~c? : Int) -> Unit {}
fn main {
let a = 1
let c = Some(3)
f(~a, ~c?) //deprecated
}

You can migrate code to the new syntax with moon fmt.

  • Reserved Keywords

A series of reserved keywords have been added for future use by the compiler. Currently:

  1. Using these reserved keywords will produce a warning, advising users to migrate.
  2. These keywords may become language keywords in future versions.

Reserved keywords are:

module
move
ref
static
super
unsafe
use
where
async
await
dyn
abstract
do
override
typeof
virtual
yield
local
method
alias
  • Planned Changes to Bytes Type

The standard library's Bytes type will be changed to an immutable type. Therefore, functions that modify Bytes will be marked as deprecated.

Alternatives for mutable byte operations:

  1. Array[Byte]
  2. FixedArray[Byte]
  3. Buffer
  • Rename of @test.is

The @test.is function has been renamed to @test.same_object. @test.is is now deprecated. The is keyword will become a reserved word in the future.

IDE Updates

  • Fixed an issue where moon fmt --block-style didn't handle document comments like ////| comment correctly.

  • IDE adapted suffix label argument syntax to support gotoref and rename.

  • Support code highlighting in documentation.

  • Fixed an issue in LSP with @ autocompletion when there are internal packages.

Build system update

  • The -verbose option now outputs the currently running command.

MoonBit ProtoBuf

2024-11-04

· 3 min read

Language Updates

  • Compile-time Constants Support
    • Introduced support for constants declared with const C = ..., where names start with uppercase letters. Must be built-in numeric types or String. Constants can be used as regular values and for pattern matching. Currently, the values of constants can only be literals.
const MIN_INT = 0x1000_0000
const MAX_INT = 0x7fff_ffff

fn classify_int(x : Int) -> Unit {
match x {
MIN_INT => println("smallest int")
MAX_INT => println("largest int")
_ => println("other int")
}
}

fn main {
classify_int(MIN_INT) // smallest int
classify_int(MAX_INT) // largest int
classify_int(42) // other int
}
  • Improved Unused Warnings
  1. Added detection for unused parameters in enum:
enum E {
// Compiler warns if y is unused
C(~x : Int, ~y : Int)
}

fn f(x : E) -> Unit {
match x {
C(~x, ..) => println(x)
}
}

fn main {
f(C(x=1, y=2))
}
  1. Added detection for unused default parameter values (default off):
// The function `f` is private and that the caller always provides a value for `x` explicitly when calling it.
// If warning 32 is enabled (which is off by default), the compiler will notify the developer that the default value for `x` is unused.
fn f(~x : Int = 0) -> Unit {
println(x)
}

fn main {
f(x=1)
}
  • Direct Function Imports
    • Functions from other packages can now be directly imported without the @pkg. prefix. To set this up, you must declare the functions in the "value" field of the moon.pkg.json configuration file.
{
"import": [
{
"path": "moonbitlang/pkg",
"alias": "pkg",
"value": ["foo", "bar"]
}
]
}

In this example, the functions foo and bar from the package moonbitlang/pkg can be called directly without the prefix @pkg..

  • Native JavaScript BigInt Support

    • BigInt type compiles to native JavaScript BigInt, with efficient pattern matching using switch statements.
  • Experimental: JavaScript backend generates .d.ts for exported functions

    • JavaScript backend now generates .d.ts files based on exported functions specified in moon.pkg.json, enhancing TypeScript/JavaScript integration. (*This feature for exporting complex types is still under design, and for now, it generates TypeScript's any type.)

IDE Updates

  • Block-line Support

    • Introduced special block-line markers (///|) in top-level comments (///) to enhance code readability and structure: block-line.jpg
    • Use moon fmt --block-style can automatically add these markers. In the future, incremental code parsing and type checking based on block-line markers will further enhance the responsiveness and usability of the Language Server Protocol (LSP), improving development efficiency.
  • MoonBit Online IDE can visit GitHub repos for instant review, edit, and test.

  1. Go to a MoonBit-based repo on GitHub.
  2. Replace github.com with try.moonbitlang.com.
  • Test Coverage Visualization

    • Added support for visualizing test coverage in the test explorer: coverage.jpg
  • AI Features

    • Added /doc-pub command for generating documentation for public functions.
    • Fixed issue where /doc command would overwrite pragmas.
    • Patch now verifies generated test cases: ai-test.jpg

Build System Updates

  • Package Checking
    • moon check supports checking specified packages and their dependencies: moon check /path/to/pkg.

MoonBit Markdown Library

  • Open Source
    • The MoonBit Markdown library is now open source, and available for download on MoonBit's package manager mooncakes.io.

2024-10-21

· 5 min read

Language Update

  • MoonBit native backend support

  • Js-string-builtins proposal support for Wasm-gc backend

    When the -use-js-builtin-string compiler option is enabled, MoonBit strings will be represented with JavaScript's string type when targeting the wasm-gc backend. This requires importing string-related functions from the JavaScript host into the generated Wasm executable. This can be achieved using the following option in the JS glue code:

// glue.js
// read wasm file
let bytes = read_file_to_bytes(module_name);
// compile the wasm module with js-string-builtin on
let module = new WebAssembly.Module(bytes, { builtins: ['js-string'], importedStringConstants: "moonbit:constant_strings" });
// instantiate the wasm module
let instance = new WebAssembly.Instance(module, spectest);
  • Integer literal overloading for Byte type
let b : Byte = 65
println(b) // b'\x41'
  • Multiline string interpolation and escape sequence support

    Since multiline strings are sometimes used to store raw strings, which may contain sequences conflicting with escape sequences, MoonBit has extended the existing multiline string interpolation syntax. Users can now control whether to enable interpolation and escape sequences for each line with the following markers:

    $|: enables interpolation and escape

    #|: marks it as a raw string.

    For example:

let a = "string"
let b = 20
let c =
#| This is a multiline string
$| \ta is \{a},
$| \tb is \{b}
#| raw string \{not a interpolation}
println(c)

Print:

 This is a multiline string
a is string,
b is 20
raw string \{not a interpolation}
  • Syntax adjustment for labeled parameters

    The syntax f(~label=value) in function calls and Constr(~label=pattern) in pattern matching has been removed. The form without the ~ symbol is now the only valid syntax: f(label=value) and Constr(label=pattern). However, f(~value) and Constr(~name) remain unaffected.

IDE Update

  • Fixed highlighting for string interpolation.

Core Update

  • Introduced StringBuilder in the Builtin package

    StringBuilder has been optimized for string concatenation operations across different backends. For example, on the JS backend, using StringBuilder results in a fivefold performance improvement compared to the previous Buffer implementation. The Buffer in the Builtin package has been deprecated, and its related APIs have been moved to the moonbitlang/core/buffer package. Future updates will involve adjustments to the Bytes and Buffer APIs.

  • Bitwise operations adjustment

    The standard library’s left and right shift functions (lsr, asr, lsl, shr, shl) have been deprecated. Only op_shl and op_shr remain. For bitwise operations like lxor, lor, land, op_shr, and op_shl, infix operators are now recommended for use.

  • Breaking change

    The Last function in immut/List now returns Option[T].

Build System Update

  • Initial support for the native backend

    • run | test | build | check now support --target native.
    • On the native backend, moon test compiles with tcc in debug mode (default) and cc in release mode (on Unix). Windows is not yet supported.
    • Panic tests are not yet supported.
  • Support for @json.inspect—objects inspected must implement ToJson.

    Example:

enum Color {
Red
} derive(ToJson)

struct Point {
x : Int
y : Int
color : Color
} derive(ToJson)

test {
@json.inspect!({ x: 0, y: 0, color: Color::Red })
}

After running moon test -u, the test block is automatically updated:

test {
@json.inspect!({ x: 0, y: 0, color: Color::Red }, content={"x":0,"y":0,"color":{"$tag":"Red"}})
}

Compared to inspect, the results of @json.inspect can be formatted with code formatting tools:

test {
@json.inspect!(
{ x: 0, y: 0, color: Color::Red },
content={ "x": 0, "y": 0, "color": { "$tag": "Red" } },
)
}

Additionally, moon test automatically performs structured comparisons on the JSON within @json.inspect.

enum Color {
Red
Green
} derive(ToJson)

struct Point {
x : Int
y : Int
z : Int
color : Color
} derive(ToJson)

test {
@json.inspect!(
{ x: 0, y: 0, z: 0, color: Color::Green },
content={ "x": 0, "y": 0, "color": { "$tag": "Red" } },
)
}

The moon test output diff for the following code will look like this:

Diff:
{
+ z: 0
color: {
- $tag: "Red"
+ $tag: "Green"
}
}
  • moon.mod.json supports include and exclude fields. These fields are arrays of strings, with each string following the same format as lines in a .gitignore file. The rules are as follows:

    • If neither include nor exclude fields exist, only the .gitignore file is considered.
    • If the exclude field exists, both the exclude field and .gitignore file are considered.
    • If the include field exists, both exclude and .gitignore are ignored; only files listed in include will be packaged.
    • The moon.mod.json file is always packaged, regardless of the rules.
    • /target and /.mooncakes are always excluded from packaging.
  • Added the moon package command for packaging files without uploading.

    • moon package --list lists all files in the package.
  • Support for moon publish --dry-run—the server will validate the package without updating the index data.

2024-10-08

· 2 min read

IDE Update

  • AI Codelens now supports /generate and /fix commands.

The /generate command provides a generic interface for generating code.

generate.gif

The /fix command reads the current function's error information and suggests fixes.

fix.gif

Language Updates

  • Adjusted the precedence of infix expressions and if, match, loop, while, for, try control flow expressions. These control flow expressions can no longer directly appear in positions requiring infix expressions and now require an additional layer of parentheses when nested.

For example, the syntax for if and match is:

if <infix-expr> { <expression> } [else { <expression> }]
match <infix-expr> { <match-cases> }

Since if, match, loop, while, for, try are no longer considered infix expressions, code like if if expr {} {} is now invalid:

// invalid
if if cond {a} else {b} {v} else {d}
match match expr { ... } { ... }
let a = expr1 + expr2 + if a {b} else {c} + expr3
// valid
if (if cond {a} else {b}) {v} else {d}
match (match expr { ... }) { ... }
let a = expr1 + expr2 + (if a {b} else {c}) + expr3
  • JavaScript backend

    • Arrays now compile to native JS arrays, making interaction with JS more convenient.
  • Standard Library API

    • Added concat and from_array functions to the String package, deprecating Array::join.
    • Added rev_concat() to the immut/list package.
    • Buffer type now includes length and is_empty functions.
    • Improved the to_json function for the Option type.
  • Experimental Library API

    • x/fs package now supports the Wasm, Wasm-gc, and JS backends, including the following APIs:
      • write_string_to_file, write_bytes_to_file
      • read_file_to_string, read_file_to_bytes
      • path_exists
      • read_dir
      • create_dir
      • is_dir, is_file
      • remove_dir, remove_file

Build System Updates

  • moon test -p now supports fuzzy matching. For example, moon test -p moonbitlang/core/builtin can be shortened to moon test -p mcb or moon test -p builtin.

  • In moon.pkg.json, if the source field is an empty string "", it is equivalent to ".", representing the current directory.

Moondoc Update

  • The documentation generator now supports package-level README files. Any README.md in the same directory as moon.pkg.json will be displayed on the package’s documentation page.

weekly 2024-09-18

· 6 min read

Language Updates

  • Simplified Field Access for type

The type system now supports passing field access to internal types.

struct UnderlyingType {
field1: Int,
field2: Bool
}

type Newtype UnderlyingType

fn main {
let newtype = Newtype({ field1: 100, field2: true })
println(newtype.field1) // 100
println(newtype.field2) // true
}

Previously, to access the field1 of UnderlyingType within newtype, you had to use newtype._.field1. Now, you can access field1 directly via newtype.field1.

  • JSON serialization via derive

Supports custom types implementing ToJson and FromJson traits via derive.

derive(ToJson) automatically generates the necessary implementation for a type, and the resulting JSON format is compatible with the auto-generated FromJson.

struct Record {
field1: Int,
field2: Enum
} derive(Show, ToJson, FromJson)

enum Enum {
Constr1(Int, Bool?),
Constr2
} derive(Show, ToJson, FromJson)

fn main {
let record = { field1: 20, field2: Constr1(5, Some(true)) }
println(record.to_json().stringify())
// Output: {"field1":20,"field2":{"$tag":"Constr1","0":5,"1":true}}
let json = record.to_json()
try {
let record: Record = @json.from_json!(json)
println(record)
// Output: {field1: 20, field2: Constr1(5, Some(true))}
} catch {
@json.JsonDecodeError(err) => println(err)
}
}
  • Guard Statement Support

Two forms, guard and guard let, to enforce invariants and reduce indentation from pattern matching.

fn init {
guard invariant else { otherwise }
continue_part
}

The invariant in guard is a Bool expression. If true, continue_part executes; otherwise, otherwise runs, and the rest of the continue_part is skipped. The else { otherwise } part is optional; if omitted, the program terminates when invariant is false.

fn init {
guard let ok_pattern = expr1 else {
fail_pattern1 => expr2
fail_pattern2 => expr3
}
continue_part
}

guard let works similarly to guard, but it supports additional pattern matching. When expr1 matches the ok_pattern, the continue_part is executed; otherwise, it tries to match the branches inside the else block.

If the else block is omitted or no branch matches, the program terminates. The ok_pattern can introduce new bindings, which are available throughout the entire continue_part. Here’s an example:

fn f(map: Map[String, Int]) -> Int!Error {
guard let Some(x) = map["key1"] else {
None => fail!("key1 not found")
}
x + 1
}
  • moonfmt Adjustments

For if, match, loop, while, for, and try expressions used outside of a statement context, parentheses will automatically be added during formatting.

Next week, we will adjust the precedence of if, match, loop, while, for, try, and infix expressions, which is a breaking change. After this adjustment, these expressions can no longer appear directly in places where infix expressions are required by syntax. For example, the following code will be considered invalid in the future:

if if cond {a} else {b} {v} else {d}
match match expr { ... } { ... }
let a = expr1 + expr2 + if a {b} else {c} + expr3
guard if a {b} else {c} else { d }

After the adjustment, the original code will require additional parentheses:

if (if cond {a} else {b}) {v} else {d}
match (match expr { ... }) { ... }
let a = expr1 + expr2 + (if a {b} else {c}) + expr3
guard (if a {b} else {c}) else { d }

After the adjustment, the original code will require additional parentheses:

We recommend using let x = y to introduce new bindings for intermediate results of if, match, and similar expressions to improve code readability without introducing extra overhead. For example:

// not suggested
match (match expr { ... }) + (if a {b} else {c}) + expr { ... }
// suggested
let r1 = match expr { ... }
let r2 = if a {b} else {c}
match r1 + r2 + expr {
...
}

Functions like .land(), lor(), shl(), and op_shr() will now use the infix operators &, |, <<, and >> after formatting.

IDE Updates

  • Global project-wide symbol search supported. signal.png
  • Fixed renaming bug that overwrote package names.
  • Optimized automatic execution of moon check during plugin use.
  • Added completion for keywords and Bool literals.
  • Adapted conditional compilation to the build system, while maintaining compatibility with the original method of distinguishing backends through file name suffixes (e.g., x.wasm.mbt, x.js.mbt).

Build System Updates

  • Added support for build graph visualization.

By passing --build-graph after moon check | build | test, a .dot file of the build graph will be generated in the corresponding build directory after compilation.

  • moon.pkg.json now includes a targets field for defining conditional compilation expressions at the file level.

These expressions support three logical operators: and, or, and not. The or operator can be omitted, so ["or", "wasm", "wasm-gc"] can be simplified to ["wasm", "wasm-gc"]. The conditions include backend types ("wasm", "wasm-gc", and "js") and optimization levels ("debug" and "release"). Nested conditions are also supported. If a file is not listed in the targets field, it will be compiled under all conditions by default.

sample:

    {
"targets": {
"only_js.mbt": ["js"],
"not_js.mbt": ["not", "js"],
"only_debug.mbt": ["and", "debug"],
"js_and_release.mbt": ["and", "js", "release"],
"js_only_test.mbt": ["js"],
"complex.mbt": ["or", ["and", "wasm", "release"], ["and", "js", "debug"]]
}
}
  • The moon.pkg.json file now includes a pre-build field for configuring pre-build commands. These commands will be executed before running moon check | build | test. The pre-build field is an array, where each element is an object containing three fields: input, output, and command.

a. input and output can be either strings or arrays of strings.

b. command is a string where you can use any command-line command, along with $input and $output variables representing input and output files (if they are arrays, they are joined with spaces).

A special built-in command :embed is available to convert files into MoonBit source code:

a. -text (default) embeds text files, while -binary embeds binary files.

b. -name specifies the generated variable name, with a default value of resource.

The commands are executed in the directory where the moon.pkg.json file resides.

Sample: moon.pkg.json

{
"pre-build": [
{
"input": "a.txt",
"output": "a.mbt",
"command": ":embed -i $input -o $output"
}
]
}

if a.txt is:

hello,
world

After executing moon build, the following a.mbt file is generated in the directory where moon.pkg.json is located:

let resource : String =
#|hello,
#|world
#|
  • moon test --target all now supports backend suffixes ([wasm], [js], etc.).
$ moon test --target all
Total tests: 0, passed: 0, failed: 0. [wasm]
Total tests: 0, passed: 0, failed: 0. [js]