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
196 lines
7.3 KiB
Elixir
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
|