llm
Download
461 lines 14.5 KiBTomoModified 2026-09-22 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
struct Vec{x,y:Int}

# A list literal can hold more than one comprehension, and each contributes its
# own items. Written without parentheses the second one is swallowed by the
# first -- `[x for x in xs, y for y in xs]` is a single comprehension nested in
# another -- so the parentheses are what keep them apart.
test "several comprehensions in one list"
    xs := [1, 2, 3]
    assert [(x for x in xs), (x*10 for x in xs)] == [1, 2, 3, 10, 20, 30]
    assert [1, (x for x in xs), 99] == [1, 1, 2, 3, 99]
    assert [(x for x in xs)] == xs

test "empty list literal with type"
    >> nums : [Float32] = []
    assert nums == []

test "empty list declaration with type"
    nums : [Float32]
    >> nums
    assert nums == []

test "indexing, length, and iteration"
    >> list := [10, 20, 30]
    assert list == [10, 20, 30]
    >> list[1]
    assert list[1] == 10
    >> list[-1]
    assert list[-1] == 30
    >> list.length
    assert list.length == 3

    >> sum := 0
    for x in list
        >> sum += x
    >> sum
    assert sum == 60

    >> str := ""
    for x at i in list
        >> str ++= "($i,$x)"
    >> str
    assert str == "(1,10)(2,20)(3,30)"

test "concatenation"
    >> list := [10, 20] ++ [30, 40]
    assert list == [10, 20, 30, 40]
    >> list ++= [50, 60]
    assert list == [10, 20, 30, 40, 50, 60]

test "concatenation is copy-on-write"
    >> list := [10, 20]
    >> copy := list
    >> list ++= [30]
    assert list == [10, 20, 30]
    >> copy
    assert copy == [10, 20]

test "list comprehensions"
    >> [10*i for i in 5]
    assert [10*i for i in 5] == [10, 20, 30, 40, 50]
    >> [i*10 for i in 5]
    assert [i*10 for i in 5] == [10, 20, 30, 40, 50]
    >> [i*10 for i in 5 if i mod 2 != 0]
    assert [i*10 for i in 5 if i mod 2 != 0] == [10, 30, 50]
    >> [x for x in y if x > 1 for y in [3, 4, 5] if y < 5]
    assert [x for x in y if x > 1 for y in [3, 4, 5] if y < 5] == [2, 3, 2, 3, 4]

test "heap-allocated list mutation"
    >> list := @[10, 20]
    >> copy := list[]
    list.insert(30)
    >> list[]
    assert list[] == [10, 20, 30]
    >> copy
    assert copy == [10, 20]
    >> list[1] = 999
    >> list[]
    assert list[] == [999, 20, 30]

test "reversed is copy-on-write"
    >> list := &[10, 20, 30]
    >> reversed := list.reversed()
    assert reversed == [30, 20, 10]
    # Ensure the copy-on-write behavior triggers:
    >> list[1] = 999
    >> reversed
    assert reversed == [30, 20, 10]

test "swap"
    >> list := &[10, 20, 30]
    list.swap(1, 3)
    assert list[] == [30, 20, 10]
    # Negative indices count from the back:
    list.swap(2, -1)
    assert list[] == [30, 10, 20]
    # Self-swap is a no-op:
    list.swap(2, 2)
    assert list[] == [30, 10, 20]

test "swap is copy-on-write"
    >> list := &[10, 20, 30]
    >> snapshot := list[]
    list.swap(1, 3)
    assert list[] == [30, 20, 10]
    assert snapshot == [10, 20, 30]

test "sorting"
    >> nums := @[10, -20, 30]
    # Sorted function doesn't mutate original:
    >> nums.sorted()
    assert nums.sorted() == [-20, 10, 30]
    >> nums[]
    assert nums[] == [10, -20, 30]
    # Sort function does mutate in place:
    nums.sort()
    >> nums[]
    assert nums[] == [-20, 10, 30]
    # Custom sort functions:
    nums.sort(func(x,y:Int) x.abs() <> y.abs())
    >> nums[]
    assert nums[] == [10, -20, 30]
    nums.sort(func(x,y:Int) y <> x)
    >> nums[]
    assert nums[] == [30, 10, -20]

test "weighted sample"
    >> ["A", "B", "C"].sample(10, [1.0, 0.5, 0.0])

test "heap operations"
    >> heap := @[(i * 1337) mod 37 for i in 10]
    heap.heapify()
    >> heap
    heap_order : @[Int]
    repeat
        heap_order.insert(heap.heap_pop() or break)
    assert heap_order[] == heap_order.sorted()
    heap_order[] = []
    for i in 10
        heap.heap_push((i*13337) mod 37)
    >> heap
    repeat
        heap_order.insert(heap.heap_pop() or break)
    assert heap_order[] == heap_order.sorted()

test "slicing with from, to, and by"
    >> [i*10 for i in 5].from(3)
    assert [i*10 for i in 5].from(3) == [30, 40, 50]
    >> [i*10 for i in 5].to(3)
    assert [i*10 for i in 5].to(3) == [10, 20, 30]
    >> [i*10 for i in 5].to(-2)
    assert [i*10 for i in 5].to(-2) == [10, 20, 30, 40]
    >> [i*10 for i in 5].from(-2)
    assert [i*10 for i in 5].from(-2) == [40, 50]
    >> [i*10 for i in 5].by(2)
    assert [i*10 for i in 5].by(2) == [10, 30, 50]
    >> [i*10 for i in 5].by(-1)
    assert [i*10 for i in 5].by(-1) == [50, 40, 30, 20, 10]
    >> [10, 20, 30, 40].by(2)
    assert [10, 20, 30, 40].by(2) == [10, 30]
    >> [10, 20, 30, 40].by(-2)
    assert [10, 20, 30, 40].by(-2) == [40, 20]
    >> [i*10 for i in 10].by(2).by(2)
    assert [i*10 for i in 10].by(2).by(2) == [10, 50, 90]
    >> [i*10 for i in 10].by(2).by(-1)
    assert [i*10 for i in 10].by(2).by(-1) == [90, 70, 50, 30, 10]

    # Test iterating over list.from() and list.to()
    >> xs := ["A", "B", "C", "D"]
    for x at i in xs.to(-2)
        for y in xs.from(i+1)
            say("$(x)$(y)")

test "binary search"
    >> nums := @[-7, -4, -1, 2, 5]
    nums.sort()
    >> nums[]
    >> [nums.binary_search(func(x:Int) x >= i) for i in nums[]]
    assert [nums.binary_search(func(x:Int) x >= i) for i in nums[]] == [1, 2, 3, 4, 5]
    nums.sort(func(a,b:Int) a.abs() <> b.abs())
    >> nums[]
    >> [nums.binary_search(func(x:Int) x.abs() >= i.abs()) for i in nums[]]
    assert [nums.binary_search(func(x:Int) x.abs() >= i.abs()) for i in nums[]] == [1, 2, 3, 4, 5]

test "find and where"
    >> ["a", "b", "c"].find("b")
    assert ["a", "b", "c"].find("b") == 2
    >> ["a", "b", "c"].find("XXX")
    assert ["a", "b", "c"].find("XXX") == none
    >> [10, 20].where(func(i:Int) i.is_prime())
    assert [10, 20].where(func(i:Int) i.is_prime()) == none
    >> [4, 5, 6].where(func(i:Int) i.is_prime())
    assert [4, 5, 6].where(func(i:Int) i.is_prime()) == 2

test "by-value predicates and comparisons"
    # By-value closures on struct elements:
    pts := @[Vec{3, 1}, Vec{1, 2}, Vec{2, 0}]
    pts.sort(func(a,b:Vec) a.x <> b.x)
    assert pts[] == [Vec{1, 2}, Vec{2, 0}, Vec{3, 1}]
    assert pts.where(func(p:Vec) p.y == 0) == 2
    # By-value closures on optional elements:
    opts : [Int?] = [3, none, 1]
    assert opts.where(func(x:Int?) x == none) == 2
    assert opts.where(func(x:Int?) x == 1) == 3
    # Mutation safety: the by-value parameter is a copy, so mutating it inside
    # the predicate cannot change the source list.
    xs := @[10, 20, 30]
    matched := xs.where(func(i:Int)
        i += 100
        return i == 130
    )
    assert matched == 3
    assert xs[] == [10, 20, 30]
    # A directly-passed lambda is compiled with by-pointer args (no adapter);
    # a closure value goes through the generic by-value adapter. Both must agree.
    is_five := func(i:Int) i == 5
    assert [1, 5, 9].where(is_five) == 2
    cmp := func(a,b:Int) b <> a
    descending := @[3, 1, 2]
    descending.sort(cmp)
    assert descending[] == [3, 2, 1]

test "pop and clear"
    >> nums := &[10, 20, 30, 40, 50]
    assert nums.pop() == 50
    >> nums[]
    assert nums[] == [10, 20, 30, 40]
    assert nums.pop(2) == 20
    >> nums[]
    assert nums[] == [10, 30, 40]
    nums.clear()
    >> nums[]
    assert nums[] == []
    assert nums.pop() == none

test "unique"
    >> [1,2,1,2,3].unique()
    assert [1,2,1,2,3].unique() == {1,2,3}

test "out-of-bounds force-unwrap panics"
    _ := [10, 20][99]!
fails "Invalid list index: 99"

test "index 0 is out of bounds (lists are 1-indexed)"
    xs := [10, 20]
    _ := xs[0]!
fails "Invalid list index: 0"

test "an empty list needs a type"
    x := []
fails_compile "I can't tell what type of items this empty list holds"

test "list elements must match the declared type"
    xs : [Int] = ["hello"]
fails_compile "I expected a Int here, but this is a Text"

test "in-place iteration by reference"
    xs := @[10, 20, 30]
    for &x in xs
        x[] += 1
    assert xs[] == [11, 21, 31]

test "in-place iteration with an index variable"
    xs := @[10, 20, 30]
    for &x at i in xs
        x[] = x[] + i
    assert xs[] == [11, 22, 33]

test "in-place iteration preserves snapshots taken before the loop"
    xs := @[1, 2, 3]
    snapshot := xs[]
    for &x in xs
        x[] = x[] * 10
    assert xs[] == [10, 20, 30]
    assert snapshot == [1, 2, 3]

test "in-place iteration supports continue and break"
    xs := @[1, 2, 3, 4, 5]
    for &x at i in xs
        if i == 2
            continue
        if i == 4
            break
        x[] += 100
    assert xs[] == [101, 2, 103, 4, 5]

test "in-place iteration runs the else block on an empty list"
    xs : @[Int] = @[]
    ran_else := no
    for &x in xs
        x[] = 99
    else
        ran_else = yes
    assert ran_else

# Shared state for the 'for &' aliasing failure tests below:
ref_loop_alias : @[Int] = @[0]

func resize_ref_loop_alias()
    ref_loop_alias.insert(99)

func copy_ref_loop_alias(-> [Int])
    return ref_loop_alias[]

test "resizing a list during a 'for &' loop fails"
    xs := @[1, 2, 3]
    ref_loop_alias = xs
    for &x in xs
        resize_ref_loop_alias()
        x[] += 1
fails "The list was resized while a 'for &' loop"

test "copying a list during a 'for &' loop fails"
    xs := @[1, 2, 3]
    ref_loop_alias = xs
    for &x in xs
        _ := copy_ref_loop_alias()
        x[] += 1
fails "A copy of the list was made while a 'for &' loop"

test "a copy made during the FINAL iteration of a 'for &' loop still fails"
    # Regression: the per-iteration guard runs at the top of each iteration, so
    # without the post-loop check a final-iteration copy escaped detection and
    # the snapshot was silently corrupted by the iteration's later writes.
    xs := @[1, 2]
    ref_loop_alias = xs
    for &x at i in xs
        if i == 2
            _ := copy_ref_loop_alias()
        x[] += 100
fails "A copy of the list was made while a 'for &' loop"

test "a copy made before a 'break' exit of a 'for &' loop still fails"
    xs := @[1, 2, 3, 4]
    ref_loop_alias = xs
    for &x at i in xs
        if i == 2
            _ := copy_ref_loop_alias()
            break
        x[] += 10
fails "A copy of the list was made while a 'for &' loop"

test "a resize during the FINAL iteration of a 'for &' loop still fails"
    xs := @[7]
    ref_loop_alias = xs
    for &x in xs
        resize_ref_loop_alias()
        x[] += 1
fails "The list was resized while a 'for &' loop"

test "the list is live inside a 'for &' loop"
    xs := @[1, 2, 3]
    for &x at i in xs
        assert xs.length == 3
        if i == 1
            xs[3] = 30 # indexed writes through the name still work and are seen live
        x[] += 100
    assert xs[] == [101, 102, 130]

test "iterating a list value by reference is a compile error"
    xs := [1, 2, 3]
    for &x in xs
        x[] += 1
fails_compile "can't be iterated by reference"

# A list literal whose elements are all compile-time constants is backed by a
# module-level `static const` array (see ConstList / is_constant). These tests
# guard that optimization: the value must survive escaping its stack frame, and
# any mutation must copy-on-write rather than write through the shared const data.
func constant_list_literal(-> [Int])
    return [10, 20, 30]

func nested_constant_literal(-> [[Int]])
    return [[1, 2], [3, 4]]

test "nested constant list literals are constant"
    assert nested_constant_literal() == [[1, 2], [3, 4]]

test "a constant list literal survives escaping its stack frame"
    # The returned value's buffer is static, so it stays valid after the
    # function that produced it has returned:
    assert constant_list_literal() == [10, 20, 30]
    got := constant_list_literal()
    assert got == [10, 20, 30]

test "mutating a constant-literal-backed list copies on write"
    list := @[10, 20, 30]
    snapshot := list[]
    list.insert(40)
    assert list[] == [10, 20, 30, 40]
    list[1] = 999
    assert list[] == [999, 20, 30, 40]
    # The snapshot and a second identical literal must read the original values,
    # proving the static const buffer was never written through:
    assert snapshot == [10, 20, 30]
    assert [10, 20, 30] == [10, 20, 30]

test "remove_at() with a count shifts only the items after the removed span"
    xs := @[1, 2, 3, 4, 5, 6]
    xs.remove_at(2, count=3)
    assert xs[] == [1, 5, 6]
    ys := @["a", "b", "c", "d", "e"]
    ys.remove_at(2, count=2)
    assert ys[] == ["a", "d", "e"]

# Fisher-Yates has to run all the way down to the first pair, or the first two
# entries never swap with each other:
test "shuffle() can reach every permutation"
    always_min := func(lo,hi:Int64 -> Int64) lo
    xs := @[1, 2, 3]
    xs.shuffle(random=always_min)
    assert xs[] == [2, 3, 1]
    pair := @[1, 2]
    pair.shuffle(random=always_min)
    assert pair[] == [2, 1]

test "a list's .length is usable as an Int64 value"
    xs := [10, 20, 30]
    assert "$(xs.length)" == "3"
    assert xs.length == Int64(3)

# A comprehension inside a list literal stands for the items it produces, so
# checking a call site has to look at what it yields, not at the comprehension
# itself (whose own type is the whole list type):
func list_arg_length(xs:[Int] -> Int)
    return xs.length

test "a comprehension is accepted directly as an argument"
    assert list_arg_length([i for i in 3]) == 3
    assert list_arg_length([i for i in 5 if i > 2]) == 3
    assert list_arg_length([i*j for i in 2 for j in 2]) == 4
    assert list_arg_length([1, 2, i for i in 3]) == 5

test "sorting an empty list leaves it empty, not none"
    # List$compact() allocated nothing for a zero-length list and left `data`
    # as NULL, which is exactly what a none list is. List$sort() compacts
    # whenever the stride differs from the item size, which it does for
    # EMPTY_LIST, so sorting an empty list quietly produced none. It read as []
    # through a non-optional type and as none the moment it reached an
    # optional one.
    empty : [Int] = []
    assert empty.sorted() == []
    maybe : [Int]? = empty.sorted()
    assert maybe != none
    assert maybe! == []

    one := [1]
    assert one.sorted() == [1]

test "insert_all into exactly-fitting free space doesn't overrun"
    # An append that exactly fills the list's free slack must not touch any
    # memory past the end of the allocation, and a mid-list insert must shift
    # exactly the elements after the insertion point.
    bytes := [Byte(i) for i in 27]
    pending : @[Byte] = @[]
    pending.insert_all(bytes.slice(1, 19)) # 19 long + 8 free
    pending.insert_all(bytes.slice(20, 27)) # exactly fills the free space
    assert pending[] == bytes

    mid := @[Byte(1), Byte(2), Byte(9), Byte(10)]
    mid.insert_all([Byte(i + 2) for i in 6], at=3)
    assert mid[] == [Byte(i) for i in 10]