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

233 lines
5.8 KiB
Elixir

defmodule CljElixir.Phase7Test do
use ExUnit.Case, async: false
defp eval!(source) do
case CljElixir.Compiler.eval_string(source) do
{:ok, result, _} -> result
{:error, errors} -> raise "Compilation failed: #{inspect(errors)}"
end
end
defp compile!(source) do
case CljElixir.Compiler.compile_to_beam(source) do
{:ok, modules} -> modules
{:error, errors} -> raise "Compilation failed: #{inspect(errors)}"
end
end
describe "m/=> function specs" do
test "simple function spec compiles" do
compile!("""
(defmodule SpecTest1
(defn hello [name]
(str "hello " name))
(m/=> hello [:=> [:cat :string] :string]))
""")
assert true
after
:code.purge(SpecTest1)
:code.delete(SpecTest1)
end
test "multi-param function spec" do
compile!("""
(defmodule SpecTest2
(defn add [a b]
(+ a b))
(m/=> add [:=> [:cat :int :int] :int]))
""")
assert true
after
:code.purge(SpecTest2)
:code.delete(SpecTest2)
end
test "optional param generates multiple specs" do
compile!("""
(defmodule SpecTest3
(defn greet
([name] (greet name "hello"))
([name greeting] (str greeting " " name)))
(m/=> greet [:=> [:cat :string [:? :string]] :string]))
""")
assert true
after
:code.purge(SpecTest3)
:code.delete(SpecTest3)
end
test "multi-arity via :function" do
compile!("""
(defmodule SpecTest4
(defn greet
([name] (greet name "hello"))
([name greeting] (str greeting " " name)))
(m/=> greet [:function
[:=> [:cat :string] :string]
[:=> [:cat :string :string] :string]]))
""")
assert true
after
:code.purge(SpecTest4)
:code.delete(SpecTest4)
end
test "spec with various types" do
compile!("""
(defmodule SpecTest5
(defn process [x]
x)
(m/=> process [:=> [:cat :any] [:or :int :string :nil]]))
""")
assert true
after
:code.purge(SpecTest5)
:code.delete(SpecTest5)
end
test "spec with map type" do
compile!("""
(defmodule SpecTest6
(defn get-name [user]
(:name user))
(m/=> get-name [:=> [:cat [:map [:name :string] [:age :int]]] :string]))
""")
assert true
after
:code.purge(SpecTest6)
:code.delete(SpecTest6)
end
test "spec with tuple return" do
compile!("""
(defmodule SpecTest7
(defn fetch [id]
#el[:ok id])
(m/=> fetch [:=> [:cat :int] [:tuple :atom :int]]))
""")
assert true
after
:code.purge(SpecTest7)
:code.delete(SpecTest7)
end
end
describe "auto @type from def schemas" do
test "def with map schema generates type" do
compile!("""
(defmodule TypeTest1
(def User [:map [:name :string] [:age :int]])
(defn get-name [user]
(:name user)))
""")
assert true
after
:code.purge(TypeTest1)
:code.delete(TypeTest1)
end
test "def with or schema" do
compile!("""
(defmodule TypeTest2
(def Status [:enum :active :inactive :pending])
(defn check [s] s))
""")
assert true
after
:code.purge(TypeTest2)
:code.delete(TypeTest2)
end
test "def with and schema" do
compile!("""
(defmodule TypeTest3
(def PositiveInt [:and :int [:> 0]])
(defn check [n] n))
""")
assert true
after
:code.purge(TypeTest3)
:code.delete(TypeTest3)
end
end
describe "schema cross-references" do
test "spec references a named schema type" do
compile!("""
(defmodule CrossRefTest1
(def User [:map [:name :string] [:age :int]])
(defn get-user [id]
{:name "alice" :age 30})
(m/=> get-user [:=> [:cat :int] User]))
""")
assert true
after
:code.purge(CrossRefTest1)
:code.delete(CrossRefTest1)
end
test "schema references another schema" do
compile!("""
(defmodule CrossRefTest2
(def PositiveInt [:and :int [:> 0]])
(def Config [:map
[:host :string]
[:port PositiveInt]
[:ssl? :boolean]])
(defn load-config []
{:host "localhost" :port 8080 :"ssl?" true}))
""")
assert true
after
:code.purge(CrossRefTest2)
:code.delete(CrossRefTest2)
end
end
describe "recursive schemas" do
test "recursive schema with registry" do
compile!("""
(defmodule RecursiveTest1
(def Tree [:schema {:registry {:tree [:or :int [:tuple [:ref :tree] [:ref :tree]]]}} [:ref :tree]])
(defn make-leaf [n] n))
""")
assert true
after
:code.purge(RecursiveTest1)
:code.delete(RecursiveTest1)
end
end
describe "functions still work correctly" do
test "module with spec can be called" do
result = eval!("""
(defmodule FuncSpecTest1
(defn hello [name]
(str "hello " name))
(m/=> hello [:=> [:cat :string] :string]))
(FuncSpecTest1/hello "world")
""")
assert result == "hello world"
after
:code.purge(FuncSpecTest1)
:code.delete(FuncSpecTest1)
end
test "module with type and spec" do
result = eval!("""
(defmodule FuncSpecTest2
(def User [:map [:name :string] [:age :int]])
(defn make-user [name age]
{:name name :age age})
(m/=> make-user [:=> [:cat :string :int] User]))
(FuncSpecTest2/make-user "alice" 30)
""")
assert result == %{name: "alice", age: 30}
after
:code.purge(FuncSpecTest2)
:code.delete(FuncSpecTest2)
end
end
end