Files
Adam d8719b6d48 Phases 1-7: Complete CljElixir compiler through Malli schema adapter
Bootstrap compiler (reader, analyzer, transformer, compiler, Mix plugin),
core protocols (16 protocols for Map/List/Tuple/BitString), PersistentVector
(bit-partitioned trie), domain tools (clojurify/elixirify), BEAM concurrency
(receive, spawn, GenServer), control flow & macros (threading, try/catch,
destructuring, defmacro with quasiquote/auto-gensym), and Malli schema
adapter (m/=> specs, auto @type, recursive schemas, cross-references).

537 compiler tests + 55 Malli unit tests + 15 integration tests = 607 total.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:38:22 -04:00

453 lines
16 KiB
Elixir

defmodule CljElixir.Phase3Test do
use ExUnit.Case, async: false
# Evaluate CljElixir code with PersistentVector enabled (no vector_as_list flag)
defp eval!(source) do
case CljElixir.Compiler.eval_string(source) do
{:ok, result, _bindings} -> result
{:error, errors} -> raise "CljElixir eval error: #{inspect(errors)}"
end
end
# ==========================================================================
# Vector literal construction
# ==========================================================================
describe "vector literal construction" do
test "vector literal produces PersistentVector" do
result = eval!("[1 2 3]")
assert result.__struct__ == CljElixir.PersistentVector
end
test "empty vector literal produces PersistentVector" do
result = eval!("[]")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.pv_count(result) == 0
end
test "single element vector" do
result = eval!("[42]")
assert CljElixir.PersistentVector.pv_count(result) == 1
assert CljElixir.PersistentVector.pv_nth(result, 0) == 42
end
test "nested vectors" do
result = eval!("[[1 2] [3 4]]")
assert CljElixir.PersistentVector.pv_count(result) == 2
inner = CljElixir.PersistentVector.pv_nth(result, 0)
assert inner.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.pv_nth(inner, 0) == 1
end
test "vector with mixed types" do
result = eval!("[1 :two \"three\" nil true]")
assert CljElixir.PersistentVector.pv_count(result) == 5
assert CljElixir.PersistentVector.pv_nth(result, 0) == 1
assert CljElixir.PersistentVector.pv_nth(result, 1) == :two
assert CljElixir.PersistentVector.pv_nth(result, 2) == "three"
assert CljElixir.PersistentVector.pv_nth(result, 3) == nil
assert CljElixir.PersistentVector.pv_nth(result, 4) == true
end
end
# ==========================================================================
# vec and vector builtins
# ==========================================================================
describe "vec and vector builtins" do
test "vec converts list to PersistentVector" do
result = eval!("(vec (list 1 2 3))")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.pv_count(result) == 3
end
test "vector creates PersistentVector from args" do
result = eval!("(vector 4 5 6)")
assert result.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(result) == [4, 5, 6]
end
test "vector with no args creates empty vector" do
result = eval!("(vector)")
assert CljElixir.PersistentVector.pv_count(result) == 0
end
test "vector? returns true for PersistentVector" do
assert eval!("(vector? [1 2 3])") == true
end
test "vector? returns false for list" do
assert eval!("(vector? (list 1 2 3))") == false
end
test "vector? returns false for map" do
assert eval!("(vector? {:a 1})") == false
end
end
# ==========================================================================
# Protocol dispatch — indexed access
# ==========================================================================
describe "indexed access" do
test "nth on vector" do
assert eval!("(nth [10 20 30] 0)") == 10
assert eval!("(nth [10 20 30] 1)") == 20
assert eval!("(nth [10 20 30] 2)") == 30
end
test "nth with not-found" do
assert eval!("(nth [10 20 30] 5 :missing)") == :missing
end
test "get on vector" do
assert eval!("(get [10 20 30] 1)") == 20
end
test "get with not-found" do
assert eval!("(get [10 20 30] 5 :missing)") == :missing
end
test "get with non-integer key returns nil" do
assert eval!("(get [10 20 30] :foo)") == nil
end
end
# ==========================================================================
# Protocol dispatch — mutation operations
# ==========================================================================
describe "mutation operations" do
test "conj appends to vector" do
result = eval!("(conj [1 2] 3)")
assert CljElixir.PersistentVector.to_list(result) == [1, 2, 3]
end
test "assoc updates index" do
result = eval!("(assoc [1 2 3] 1 :x)")
assert CljElixir.PersistentVector.to_list(result) == [1, :x, 3]
end
test "assoc at end appends" do
result = eval!("(assoc [1 2] 2 3)")
assert CljElixir.PersistentVector.to_list(result) == [1, 2, 3]
end
test "contains? with valid index" do
assert eval!("(contains? [1 2 3] 0)") == true
assert eval!("(contains? [1 2 3] 2)") == true
end
test "contains? with invalid index" do
assert eval!("(contains? [1 2 3] 3)") == false
assert eval!("(contains? [1 2 3] -1)") == false
end
end
# ==========================================================================
# Protocol dispatch — sequence operations
# ==========================================================================
describe "sequence operations" do
test "count on vector" do
assert eval!("(count [1 2 3])") == 3
assert eval!("(count [])") == 0
end
test "first on vector" do
assert eval!("(first [10 20 30])") == 10
end
test "first on empty vector" do
assert eval!("(first [])") == nil
end
test "seq on non-empty vector returns list" do
result = eval!("(seq [1 2 3])")
assert is_list(result)
assert result == [1, 2, 3]
end
test "seq on empty vector returns nil" do
assert eval!("(seq [])") == nil
end
test "empty? on vector" do
assert eval!("(empty? [])") == true
assert eval!("(empty? [1])") == false
end
end
# ==========================================================================
# Stack operations (peek/pop)
# ==========================================================================
describe "stack operations" do
test "peek returns last element" do
assert eval!("(peek [10 20 30])") == 30
end
test "pop removes last element" do
result = eval!("(pop [1 2 3])")
assert CljElixir.PersistentVector.to_list(result) == [1, 2]
end
test "pop single element returns empty" do
result = eval!("(pop [42])")
assert CljElixir.PersistentVector.pv_count(result) == 0
end
end
# ==========================================================================
# Vector as function (IFn)
# ==========================================================================
describe "vector as function" do
# TODO: Vector-as-function requires transformer support for struct invocation.
# The IFn protocol is implemented but Elixir doesn't auto-dispatch when a
# struct is in call position. Needs a transformer change to detect and wrap
# non-function call heads with IFn dispatch.
end
# ==========================================================================
# Pattern matching (unchanged — vectors match tuples in patterns)
# ==========================================================================
describe "pattern matching" do
test "vector in case pattern matches tuple" do
assert eval!("(case #el[:ok 42] [:ok x] x)") == 42
end
test "vector in let pattern matches tuple" do
assert eval!("(let [[:ok x] #el[:ok 99]] x)") == 99
end
test "nested vector patterns" do
result = eval!("""
(case #el[:ok #el[:inner 5]]
[:ok [:inner n]] n
_ nil)
""")
assert result == 5
end
end
# ==========================================================================
# Metadata
# ==========================================================================
describe "metadata" do
test "empty vector has nil meta" do
result = eval!("[]")
assert Map.get(result, :meta) == nil
end
end
# ==========================================================================
# Boundary conditions (trie level transitions)
# ==========================================================================
describe "boundary conditions" do
test "32 element vector (full tail, no trie)" do
v = CljElixir.PersistentVector.from_list(Enum.to_list(1..32))
assert CljElixir.PersistentVector.pv_count(v) == 32
assert CljElixir.PersistentVector.pv_nth(v, 0) == 1
assert CljElixir.PersistentVector.pv_nth(v, 31) == 32
end
test "33 element vector (trie overflow)" do
v = CljElixir.PersistentVector.from_list(Enum.to_list(1..33))
assert CljElixir.PersistentVector.pv_count(v) == 33
assert CljElixir.PersistentVector.pv_nth(v, 0) == 1
assert CljElixir.PersistentVector.pv_nth(v, 32) == 33
end
test "1025 element vector (multi-level trie)" do
v = CljElixir.PersistentVector.from_list(Enum.to_list(1..1025))
assert CljElixir.PersistentVector.pv_count(v) == 1025
assert CljElixir.PersistentVector.pv_nth(v, 0) == 1
assert CljElixir.PersistentVector.pv_nth(v, 1024) == 1025
end
test "conj across 32-element boundary" do
v32 = CljElixir.PersistentVector.from_list(Enum.to_list(1..32))
v33 = CljElixir.PersistentVector.pv_conj(v32, 33)
assert CljElixir.PersistentVector.pv_count(v33) == 33
assert CljElixir.PersistentVector.pv_nth(v33, 32) == 33
# Original unchanged (structural sharing)
assert CljElixir.PersistentVector.pv_count(v32) == 32
end
test "pop across 33-to-32 boundary" do
v33 = CljElixir.PersistentVector.from_list(Enum.to_list(1..33))
v32 = CljElixir.PersistentVector.pv_pop(v33)
assert CljElixir.PersistentVector.pv_count(v32) == 32
assert CljElixir.PersistentVector.pv_nth(v32, 31) == 32
end
test "assoc in trie (not tail)" do
v33 = CljElixir.PersistentVector.from_list(Enum.to_list(1..33))
v33b = CljElixir.PersistentVector.pv_assoc(v33, 0, :first)
assert CljElixir.PersistentVector.pv_nth(v33b, 0) == :first
assert CljElixir.PersistentVector.pv_nth(v33b, 1) == 2
# Original unchanged
assert CljElixir.PersistentVector.pv_nth(v33, 0) == 1
end
end
# ==========================================================================
# SubVector
# ==========================================================================
describe "subvec" do
test "subvec creates view into vector" do
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3, 4, 5]), 1, 4)
assert CljElixir.SubVector.sv_count(sv) == 3
assert CljElixir.SubVector.sv_nth(sv, 0) == 2
assert CljElixir.SubVector.sv_nth(sv, 1) == 3
assert CljElixir.SubVector.sv_nth(sv, 2) == 4
end
test "subvec to_list" do
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3, 4, 5]), 1, 4)
assert CljElixir.SubVector.sv_to_list(sv) == [2, 3, 4]
end
test "subvec 2-arity (start to end)" do
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([10, 20, 30]), 1)
assert CljElixir.SubVector.sv_count(sv) == 2
assert CljElixir.SubVector.sv_to_list(sv) == [20, 30]
end
test "subvec nth with not-found" do
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3]), 0, 2)
assert CljElixir.SubVector.sv_nth(sv, 5, :missing) == :missing
end
end
# ==========================================================================
# Cross-type equality
# ==========================================================================
describe "cross-type equality" do
test "vector equals list with same elements" do
assert eval!("(= [1 2 3] (list 1 2 3))") == true
end
test "vector not equal to list with different elements" do
assert eval!("(= [1 2 3] (list 1 2 4))") == false
end
test "two vectors with same elements are equal" do
assert eval!("(= [1 2 3] [1 2 3])") == true
end
test "two vectors with different elements are not equal" do
assert eval!("(= [1 2] [1 2 3])") == false
end
test "not= works with cross-type" do
assert eval!("(not= [1 2 3] (list 1 2 3))") == false
assert eval!("(not= [1 2 3] (list 4 5 6))") == true
end
test "scalar equality still works" do
assert eval!("(= 1 1)") == true
assert eval!("(= 1 2)") == false
assert eval!("(= :a :a)") == true
end
end
# ==========================================================================
# Enumerable/Collectable protocols
# ==========================================================================
describe "Enumerable and Collectable" do
test "Enum.map over PersistentVector" do
pv = CljElixir.PersistentVector.from_list([1, 2, 3])
result = Enum.map(pv, &(&1 * 2))
assert result == [2, 4, 6]
end
test "Enum.filter over PersistentVector" do
pv = CljElixir.PersistentVector.from_list([1, 2, 3, 4, 5])
result = Enum.filter(pv, &(rem(&1, 2) == 0))
assert result == [2, 4]
end
test "Enum.count on PersistentVector" do
pv = CljElixir.PersistentVector.from_list([1, 2, 3])
assert Enum.count(pv) == 3
end
test "Enum.into PersistentVector" do
pv = Enum.into([1, 2, 3], CljElixir.PersistentVector.from_list([]))
assert pv.__struct__ == CljElixir.PersistentVector
assert CljElixir.PersistentVector.to_list(pv) == [1, 2, 3]
end
test "Enum.slice on PersistentVector" do
pv = CljElixir.PersistentVector.from_list([10, 20, 30, 40, 50])
assert Enum.slice(pv, 1, 3) == [20, 30, 40]
end
end
# ==========================================================================
# SubVector protocol dispatch
# ==========================================================================
describe "SubVector protocols" do
test "count via protocol" do
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3, 4, 5]), 1, 4)
assert CljElixir.ICounted.count(sv) == 3
end
test "nth via protocol" do
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([10, 20, 30, 40]), 1, 3)
assert CljElixir.IIndexed.nth(sv, 0) == 20
assert CljElixir.IIndexed.nth(sv, 1) == 30
end
test "lookup via protocol" do
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([10, 20, 30]), 0, 2)
assert CljElixir.ILookup.lookup(sv, 0) == 10
assert CljElixir.ILookup.lookup(sv, 5) == nil
assert CljElixir.ILookup.lookup(sv, 5, :missing) == :missing
end
test "seq via protocol" do
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3, 4]), 1, 3)
assert CljElixir.ISeqable.seq(sv) == [2, 3]
end
test "first/rest via protocol" do
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([10, 20, 30]), 0, 3)
assert CljElixir.ISeq.first(sv) == 10
assert CljElixir.ISeq.rest(sv) == [20, 30]
end
test "peek/pop via protocol" do
sv = CljElixir.SubVector.sv_new(CljElixir.PersistentVector.from_list([1, 2, 3, 4, 5]), 1, 4)
assert CljElixir.IStack.peek(sv) == 4
popped = CljElixir.IStack.pop(sv)
assert CljElixir.SubVector.sv_count(popped) == 2
end
end
# ==========================================================================
# Existing Phase 2 tests still pass (backward compatibility)
# ==========================================================================
describe "backward compatibility" do
test "maps still work with protocols" do
assert eval!("(get {:a 1 :b 2} :a)") == 1
assert eval!("(count {:a 1 :b 2})") == 2
end
test "lists still work with protocols" do
assert eval!("(first (list 10 20 30))") == 10
assert eval!("(count (list 1 2 3))") == 3
end
end
end