Files
bux-lang/tests/borrow_test.nim
T
dimgigov 53b43b0f79 feat: lifetime elision, tooling CI, registry, and LSP locals
Ship the QUALITY_PLAN stretch from ownership through ecosystem: C.1
lifetime elision (bootstrap + selfhost), bux fmt/test/doc CI hooks,
stdlib goldens, package registry (bux search/add), and LSP 0.4
position-sensitive locals with inferred let types. Full-tree format
pass plus Map/Set remove double-free fix.
2026-07-19 16:35:08 +03:00

373 lines
7.3 KiB
Nim

import std/[unittest, strutils]
import ../bootstrap/[lexer, parser, sema]
proc checkSource(source: string): tuple[hasErrors: bool, diagnostics: seq[SemaDiagnostic]] =
let lexRes = lexer.tokenize(source, "test.bux")
if lexRes.hasErrors:
return (true, @[])
let parseRes = parser.parse(lexRes.tokens, "test.bux")
if parseRes.diagnostics.len > 0:
return (true, @[])
let (semaRes, _) = sema.analyzeFull(parseRes.module)
return (semaRes.hasErrors, semaRes.diagnostics)
suite "Borrow Checker":
test "@[Checked] function with &mut allows mutation":
let res = checkSource("""
@[Checked]
func Inc(p: &mut int) {
*p = *p + 1;
}
@[Checked]
func Main() -> int {
var x: int = 5;
Inc(&x);
return x;
}
""")
check(not res.hasErrors)
test "@[Checked] function rejects write through &T":
let res = checkSource("""
@[Checked]
func BadWrite(p: &int) {
*p = 42;
}
@[Checked]
func Main() -> int {
var x: int = 5;
BadWrite(&x);
return x;
}
""")
check(res.hasErrors)
test "unchecked function allows write through raw pointer":
let res = checkSource("""
func RawWrite(p: *int) {
*p = 42;
}
func Main() -> int {
var x: int = 5;
RawWrite(&x);
return x;
}
""")
check(not res.hasErrors)
test "&T allows reading":
let res = checkSource("""
@[Checked]
func Get(p: &int) -> int {
return *p;
}
@[Checked]
func Main() -> int {
var x: int = 5;
return Get(&x);
}
""")
check(not res.hasErrors)
test "own T type parses and resolves":
let res = checkSource("""
struct Box {
value: int;
}
func TakeOwn(b: own Box) -> int {
return b.value;
}
func Main() -> int {
var b: Box = Box { value: 42 };
return TakeOwn(b);
}
""")
check(not res.hasErrors)
test "@[Checked] rejects double mutable borrow in call":
let res = checkSource("""
@[Checked]
func Swap(a: &mut int, b: &mut int) {
let tmp = *a;
*a = *b;
*b = tmp;
}
@[Checked]
func Main() -> int {
var x: int = 10;
Swap(&x, &x);
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("mutable borrow"))
test "@[Checked] rejects use after move":
let res = checkSource("""
struct Box {
value: int;
}
@[Checked]
func Consume(b: own Box) -> int {
return b.value;
}
@[Checked]
func Main() -> int {
var b: Box = Box { value: 42 };
let x: int = Consume(b);
return b.value;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("moved"))
test "@[Checked] allows reinitialization after move":
let res = checkSource("""
struct Box {
value: int;
}
@[Checked]
func Take(b: own Box) -> int {
return b.value;
}
@[Checked]
func Main() -> int {
var b: own Box = Box { value: 1 };
Take(b);
b = Box { value: 2 };
return b.value;
}
""")
check(not res.hasErrors)
test "@[Checked] move in assignment":
let res = checkSource("""
struct Box {
value: int;
}
@[Checked]
func Main() -> int {
var a: own Box = Box { value: 1 };
var b: own Box = a;
return a.value;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("moved"))
test "@[Checked] move in return":
let res = checkSource("""
struct Box {
value: int;
}
@[Checked]
func Give() -> own Box {
var x: own Box = Box { value: 42 };
return x;
}
@[Checked]
func Main() -> int {
let b: own Box = Give();
return b.value;
}
""")
check(not res.hasErrors)
test "borrow &mut expr parses and type-checks":
let res = checkSource("""
struct Point {
x: int;
y: int;
}
func Main() -> int {
var p: Point;
p.x = 10;
let r: &mut Point = borrow &mut p;
return 0;
}
""")
check(not res.hasErrors)
test "@[Shared] attribute parses without errors":
let res = checkSource("""
@[Shared]
func SharedFunc() -> int {
return 42;
}
func Main() -> int {
return SharedFunc();
}
""")
check(not res.hasErrors)
test "@[Checked] rejects two let-bound &mut of same var":
let res = checkSource("""
@[Checked]
func Main() -> int {
var x: int = 1;
let a: &mut int = &x;
let b: &mut int = &x;
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("already mutably borrowed"))
test "@[Checked] rejects assign while mutably borrowed":
let res = checkSource("""
@[Checked]
func Main() -> int {
var x: int = 1;
let a: &mut int = &x;
x = 2;
return *a;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("mutably borrowed"))
test "@[Checked] rejects shared borrow while mutably borrowed":
let res = checkSource("""
@[Checked]
func Main() -> int {
var x: int = 1;
let a: &mut int = &x;
let b: &int = &x;
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("shared-borrow") or
res.diagnostics[0].message.contains("mutably borrowed"))
test "borrow & expr with shared ref":
let res = checkSource("""
struct Point {
x: int;
y: int;
}
func Main() -> int {
var p: Point;
p.x = 10;
let r: &Point = borrow &p;
let val: int = (*r).x;
return val;
}
""")
check(not res.hasErrors)
# --- C.1 Lifetime elision ---
test "@[Checked] elided lifetime: return param ref is OK":
let res = checkSource("""
@[Checked]
func Identity(p: &int) -> &int {
return p;
}
@[Checked]
func Main() -> int {
var x: int = 7;
let r: &int = Identity(&x);
return *r;
}
""")
check(not res.hasErrors)
test "@[Checked] explicit lifetime 'a works":
let res = checkSource("""
@[Checked]
func Identity<'a>(p: &'a int) -> &'a int {
return p;
}
@[Checked]
func Main() -> int {
var x: int = 3;
let r: &int = Identity(&x);
return *r;
}
""")
check(not res.hasErrors)
test "@[Checked] rejects return of reference to local":
let res = checkSource("""
@[Checked]
func Dangle(p: &int) -> &int {
var x: int = 1;
return &x;
}
@[Checked]
func Main() -> int {
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("local"))
test "@[Checked] rejects return ref with no input reference":
let res = checkSource("""
@[Checked]
func Bad() -> &int {
var x: int = 1;
return &x;
}
@[Checked]
func Main() -> int {
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("no input reference") or
res.diagnostics[0].message.contains("local"))
test "@[Checked] elision fails with multiple input refs":
let res = checkSource("""
@[Checked]
func Pick(a: &int, b: &int) -> &int {
return a;
}
@[Checked]
func Main() -> int {
return 0;
}
""")
check(res.hasErrors)
check(res.diagnostics[0].message.contains("lifetime elision failed") or
res.diagnostics[0].message.contains("lifetime mismatch"))
test "@[Checked] multiple inputs OK with explicit lifetime":
let res = checkSource("""
@[Checked]
func Pick<'a>(a: &'a int, b: &'a int) -> &'a int {
return a;
}
@[Checked]
func Main() -> int {
var x: int = 1;
var y: int = 2;
let r: &int = Pick(&x, &y);
return *r;
}
""")
check(not res.hasErrors)
test "@[Checked] let-bound reborrow of param may be returned":
let res = checkSource("""
@[Checked]
func ViaLet(p: &int) -> &int {
let r: &int = p;
return r;
}
@[Checked]
func Main() -> int {
var x: int = 9;
return *ViaLet(&x);
}
""")
check(not res.hasErrors)
test "unchecked may return &local (no lifetime checks)":
let res = checkSource("""
func Dangle() -> &int {
var x: int = 1;
return &x;
}
func Main() -> int {
return 0;
}
""")
check(not res.hasErrors)