fe3b1e8b6a
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled
Sessions 56–69: declarative macro! with rep/zip/literal/block and unhygienic var $name binders; partial field-move skip Drop; @[Release] polish; LSP type hierarchy; CI Nim cache + lean macOS + Windows smoke.
416 lines
8.2 KiB
Nim
416 lines
8.2 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)
|
|
|
|
test "@[Release] alone disables checks (zero-cost path)":
|
|
let res = checkSource("""
|
|
@[Release]
|
|
func Dangle() -> &int {
|
|
var x: int = 1;
|
|
return &x;
|
|
}
|
|
func Main() -> int {
|
|
return 0;
|
|
}
|
|
""")
|
|
check(not res.hasErrors)
|
|
|
|
test "@[Checked] @[Release] — Release wins, no use-after-move error":
|
|
let res = checkSource("""
|
|
@[Checked]
|
|
@[Release]
|
|
func Consume(s: own String) {
|
|
// move then use — allowed because Release disables checker
|
|
let t: own String = s;
|
|
let u: own String = s;
|
|
}
|
|
func Main() -> int {
|
|
return 0;
|
|
}
|
|
""")
|
|
check(not res.hasErrors)
|
|
|
|
test "@[Checked] still errors without Release":
|
|
let res = checkSource("""
|
|
@[Checked]
|
|
func Bad() -> &int {
|
|
var x: int = 1;
|
|
return &x;
|
|
}
|
|
func Main() -> int {
|
|
return 0;
|
|
}
|
|
""")
|
|
check(res.hasErrors)
|
|
check(res.diagnostics[0].message.contains("local") or
|
|
res.diagnostics[0].message.contains("reference")) |