A statically typed language exploring compile-time memory safety without lifetime annotations — designed and implemented from scratch in C++20, compiling down to yvm, a custom bytecode virtual machine.
Most memory-safe languages force a choice: limiting static checking (Rust), or a runtime garbage collector (Go, Java, Swift). This compiler explores a different approach to static checking.
The borrow checker runs a dataflow analysis directly over each function's control-flow graph, tracking every variable and field access as it flows through branches and loops, to catch use-after-free and aliasing bugs at compile time.
The syntax itself borrows deliberately from Herb Sutter's cpp2/cppfront proposal: every declaration reads as name: kind = value, which keeps the grammar small and mostly unambiguous to parse.
The language compiles in five linear stages. Source is scanned, parsed, type-checked, and borrow-checked, then lowered to bytecode for yvm — a virtual machine built specifically for the language.
Yoyo Viz ↗ is a companion Qt/QML application built alongside the compiler. It pairs a live code editor with panes onto the compiler's own internal state — the parsed AST, the type checker's constraints and substitutions, and the borrow checker's CFG-based IR and points-to graph — recompiling as you type.
AST tree inspect the parsed tree for any snippet, node by nodeType checker watch constraints get generated and solved, and substitutions appliedBorrow checker IR the CFG-based instructions the borrow checker actually reasons overPoints-to graph which values a reference could be pointing to at a given program pointGeneric structs with explicit lifetime parameters on borrowed methods, plus operator overloading used to define an implicit reference conversion between related types.
Point: struct::<T> = { x: T, y: T, get_x: fn(&'a this)(a) -> &T = return &this.x; get_y: fn(&'a this)(a) -> &T = return &this.y; } Point3: struct::<T> = { xy: Point::<T>, z: T, get_z: fn(&'a this)(a) -> &T = return &this.z; } operator: &::<T>(inp: &'a Point3::<T>)(a) -> &Point::<T> = return &inp.xy; main: fn = { x: f32 = 100.0; y := 20.0; value := Point{ .x, .y }; x_ref: mut = value.get_x(); y_ref: mut = value.get_y(); test::print("${*x_ref}, ${*y_ref}"); other := Point3 { .xy = Point{ .x, .y }, .z = 40.0 }; x_ref = other.get_x(); y_ref = other.get_y(); z_ref := other.get_z(); test::print("${*x_ref}, ${*y_ref}, ${*z_ref}"); }
fn(&'a this)(a) -> &T a method borrowing this under lifetime 'a, returning a reference tied to itoperator: &::<T>(...) overloads the borrow operator to define an implicit reference conversionother.get_x() works on a Point3 because the operator above converts &Point3 to &Pointvalue.get_x() calling a method through &T to get a field reference.x, .y shorthand struct-literal fields, filled from same-named locals${expr} string interpolation inside a string literalSSA-based dataflow over each function's control-flow graph catches aliasing and use-after-free.
Compiles to bytecode for yvm, a virtual machine built specifically for the language, after the original LLVM backend was retired.
Generic structs and functions, plus interface-style impl blocks, resolved at compile time.
Optional values are unwrapped with if |val| (expr), binding the contained value only on the non-null path.
A cppfront-inspired name: kind = value declaration form used consistently throughout the language.
Meson build system, with Catch2 unit tests covering the parser and IR generator.