elipl/test/support/test_helper.ex
Kacper Marzecki 748f87636a checkpoint
checkpoint

failing test

after fixing tests

checkpoint

checkpoint

checkpoint

re-work

asd

checkpoint

checkpoint

checkpoint

mix proj

checkpoint mix

first parser impl

checkpoint

fix tests

re-org parser

checkpoint strings

fix multiline strings

tuples

checkpoint maps

checkpoint

checkpoint

checkpoint

checkpoint

fix weird eof expression parse error

checkpoint before typing

checkpoint

checpoint

checkpoint

checkpoint

checkpoint ids in primitive types

checkpoint

checkpoint

fix tests

initial annotation

checkpoint

checkpoint

checkpoint

union subtyping

conventions

refactor - split typer

typing tuples

checkpoint test refactor

checkpoint test refactor

parsing atoms

checkpoint atoms

wip lists

checkpoint typing lists

checkopint

checkpoint

wip fixing

correct list typing

map discussion

checkpoint map basic typing

fix tests checkpoint

checkpoint

checkpoint

checkpoint

fix condition typing

fix literal keys in map types

checkpoint union types

checkpoint union type

checkpoint row types discussion & bidirectional typecheck

checkpoint

basic lambdas

checkpoint lambdas typing application

wip function application

checkpoint

checkpoint

checkpoint cduce

checkpoint

checkpoint

checkpoint

checkpoint

checkpoint

checkpoint

checkpoint
2025-06-13 23:48:07 +02:00

196 lines
7.3 KiB
Elixir

defmodule Til.TestHelpers do
import ExUnit.Assertions
alias Til.Parser
# For type-checking related helpers
alias Til.Typer
alias Til.AstUtils
# --- Node Access Helpers ---
def get_file_node_from_map(nodes_map) do
file_node =
Enum.find(Map.values(nodes_map), fn node ->
# Ensure the map is an AST node before checking its type
Map.has_key?(node, :ast_node_type) && node.ast_node_type == :file
end)
refute is_nil(file_node), "File node not found in #{inspect(nodes_map)}"
file_node
end
def get_node_by_id(nodes_map, node_id) do
node = Map.get(nodes_map, node_id)
refute is_nil(node), "Node with ID #{inspect(node_id)} not found in nodes_map."
node
end
def get_first_child_node(nodes_map, parent_node_id \\ nil) do
parent_node =
if is_nil(parent_node_id) do
get_file_node_from_map(nodes_map)
else
get_node_by_id(nodes_map, parent_node_id)
end
children_ids = Map.get(parent_node, :children, [])
if Enum.empty?(children_ids) do
flunk("Parent node #{parent_node.id} has no children, cannot get first child node.")
end
get_node_by_id(nodes_map, hd(children_ids))
end
def get_nth_child_node(nodes_map, index, parent_node_id \\ nil) do
parent_node =
if is_nil(parent_node_id) do
get_file_node_from_map(nodes_map)
else
get_node_by_id(nodes_map, parent_node_id)
end
children_ids = Map.get(parent_node, :children, [])
unless index >= 0 && index < length(children_ids) do
flunk(
"Child node at index #{index} not found for parent #{parent_node.id}. Parent has #{length(children_ids)} children. Children IDs: #{inspect(children_ids)}"
)
end
get_node_by_id(nodes_map, Enum.at(children_ids, index))
end
# --- Combined Parse/TypeCheck and Get Node Helpers ---
# These return {node, nodes_map}
def parse_and_get_first_node(source_string, file_name \\ "test_source") do
{:ok, parsed_nodes_map} = Parser.parse(source_string, file_name)
{get_first_child_node(parsed_nodes_map), parsed_nodes_map}
end
def parse_and_get_nth_node(source_string, index, file_name \\ "test_source") do
{:ok, parsed_nodes_map} = Parser.parse(source_string, file_name)
{get_nth_child_node(parsed_nodes_map, index), parsed_nodes_map}
end
def typecheck_and_get_first_node(source_string, file_name \\ "test_source") do
{:ok, parsed_nodes_map} = Parser.parse(source_string, file_name)
{:ok, typed_nodes_map} = Typer.type_check(parsed_nodes_map)
{get_first_child_node(typed_nodes_map), typed_nodes_map}
end
def typecheck_and_get_nth_node(source_string, index, file_name \\ "test_source") do
{:ok, parsed_nodes_map} = Parser.parse(source_string, file_name)
{:ok, typed_nodes_map} = Typer.type_check(parsed_nodes_map)
{get_nth_child_node(typed_nodes_map, index), typed_nodes_map}
end
# --- Type Assertion Helpers ---
# Strips :id fields recursively and resolves _id suffixed keys
# from a type definition structure, using nodes_map for lookups.
def deep_strip_id(type_definition, nodes_map) do
cond do
is_struct(type_definition, MapSet) ->
type_definition
|> MapSet.to_list()
# Recursively call on elements
|> Enum.map(&deep_strip_id(&1, nodes_map))
|> MapSet.new()
is_map(type_definition) ->
# Remove its own :id.
map_without_id = Map.delete(type_definition, :id)
# Recursively process its values, resolving _id keys.
# Special handling for type_annotation_mismatch error details
# Check if this map is a type definition itself (has :type_kind) before specific error checks
if Map.has_key?(type_definition, :type_kind) &&
type_definition.type_kind == :error &&
type_definition.reason == :type_annotation_mismatch do
actual_type_clean =
case Map.get(type_definition, :actual_type_id) do
nil -> nil
id -> deep_strip_id(Map.get(nodes_map, id), nodes_map)
end
expected_type_clean =
case Map.get(type_definition, :expected_type_id) do
nil -> nil
id -> deep_strip_id(Map.get(nodes_map, id), nodes_map)
end
%{
type_kind: :error,
reason: :type_annotation_mismatch,
actual_type: actual_type_clean,
expected_type: expected_type_clean
}
else
# Standard processing for other maps
Enum.reduce(map_without_id, %{}, fn {original_key, original_value}, acc_map ->
{final_key, final_value} =
cond do
# Handle :element_type_id -> :element_type
original_key == :element_type_id && is_atom(original_value) ->
resolved_def = Map.get(nodes_map, original_value)
{:element_type, deep_strip_id(resolved_def, nodes_map)}
# Handle :key_type_id -> :key_type
original_key == :key_type_id && is_atom(original_value) ->
resolved_def = Map.get(nodes_map, original_value)
{:key_type, deep_strip_id(resolved_def, nodes_map)}
# Handle :value_type_id -> :value_type (e.g., in index_signature or %{value_type_id: ..., optional: ...})
original_key == :value_type_id && is_atom(original_value) ->
resolved_def = Map.get(nodes_map, original_value)
{:value_type, deep_strip_id(resolved_def, nodes_map)}
is_map(original_value) ->
{original_key, deep_strip_id(original_value, nodes_map)}
is_list(original_value) ->
{original_key, deep_strip_id(original_value, nodes_map)} # Handles lists of type defs
true ->
{original_key, original_value}
end
Map.put(acc_map, final_key, final_value)
end)
end
is_list(type_definition) ->
# Recursively call on elements for lists of type definitions
Enum.map(type_definition, &deep_strip_id(&1, nodes_map))
true -> # Literals, atoms, numbers, nil, etc. (leaf nodes in the type structure)
type_definition
end
end
def assert_node_typed_as(node, typed_nodes_map, expected_type_definition_clean) do
type_key = node.type_id
assert is_atom(type_key),
"Type ID for node #{inspect(node.id)} (#{node.raw_string}) should be an atom key, got: #{inspect(type_key)}"
actual_type_definition_from_map = Map.get(typed_nodes_map, type_key)
refute is_nil(actual_type_definition_from_map),
"Type definition for key #{inspect(type_key)} (from node #{node.id}, raw: '#{node.raw_string}') not found in nodes_map."
actual_type_definition_clean =
deep_strip_id(actual_type_definition_from_map, typed_nodes_map)
assert actual_type_definition_clean == expected_type_definition_clean,
"Type mismatch for node #{node.id} ('#{node.raw_string}').\nExpected (clean):\n#{inspect(expected_type_definition_clean, pretty: true, limit: :infinity)}\nGot (clean):\n#{inspect(actual_type_definition_clean, pretty: true, limit: :infinity)}"
end
def inspect_nodes(node_map) do
AstUtils.build_debug_ast_data(node_map)
|> IO.inspect(pretty: true, limit: :infinity)
end
end