# defmodule Til.TypingSimpleTest do # use ExUnit.Case, async: true # # lets always alias AstUtils, Typer and Parser for convenience # alias Til.AstUtils # alias Til.Parser # alias Til.Typer # # Added alias # alias Til.TestHelpers # # alias MapSet, as: Set # # describe "simple type inference tests" do # test "types a literal integer" do # source = "42" # {integer_node, typed_nodes_map} = TestHelpers.typecheck_and_get_first_node(source) # # assert integer_node.ast_node_type == :literal_integer # # TestHelpers.assert_node_typed_as(integer_node, typed_nodes_map, %{ # type_kind: :literal, # value: 42 # }) # end # # test "types a literal string" do # source = "'hello'" # {string_node, typed_nodes_map} = TestHelpers.typecheck_and_get_first_node(source) # # assert string_node.ast_node_type == :literal_string # # TestHelpers.assert_node_typed_as(string_node, typed_nodes_map, %{ # type_kind: :literal, # value: "hello" # }) # end # # # test "types a simple s-expression (e.g. function call with literals)" do # # source = "(add 1 2)" # # {:ok, nodes_map} = Parser.parse(source) # # # # flunk("Test not implemented: #{inspect(nodes_map)}") # # end # # test "types an s-expression with a symbol lookup in an environment" do # source = """ # (= x 5) # (= y x) # """ # # # Parse once # {:ok, parsed_nodes_map} = Parser.parse(source) # # Typecheck once # {:ok, typed_nodes_map} = Typer.type_check(parsed_nodes_map) # # file_node = Enum.find(Map.values(typed_nodes_map), &(&1.ast_node_type == :file)) # refute is_nil(file_node) # assert length(file_node.children) == 2, "Expected two top-level s-expressions" # # # --- First assignment: (= x 5) --- # s_expr_1_node = TestHelpers.get_nth_child_node(typed_nodes_map, 0, file_node.id) # assert s_expr_1_node.ast_node_type == :s_expression # assert length(s_expr_1_node.children) == 3 # # symbol_x_lhs_node = TestHelpers.get_nth_child_node(typed_nodes_map, 1, s_expr_1_node.id) # assert symbol_x_lhs_node.ast_node_type == :symbol # assert symbol_x_lhs_node.name == "x" # # literal_5_node = TestHelpers.get_nth_child_node(typed_nodes_map, 2, s_expr_1_node.id) # assert literal_5_node.ast_node_type == :literal_integer # assert literal_5_node.value == 5 # # TestHelpers.assert_node_typed_as(literal_5_node, typed_nodes_map, %{ # type_kind: :literal, # value: 5 # }) # # TestHelpers.assert_node_typed_as(s_expr_1_node, typed_nodes_map, %{ # type_kind: :literal, # value: 5 # }) # # # --- Second assignment: (= y x) --- # s_expr_2_node = TestHelpers.get_nth_child_node(typed_nodes_map, 1, file_node.id) # assert s_expr_2_node.ast_node_type == :s_expression # assert length(s_expr_2_node.children) == 3 # # symbol_y_lhs_node = TestHelpers.get_nth_child_node(typed_nodes_map, 1, s_expr_2_node.id) # assert symbol_y_lhs_node.ast_node_type == :symbol # assert symbol_y_lhs_node.name == "y" # # symbol_x_rhs_node = TestHelpers.get_nth_child_node(typed_nodes_map, 2, s_expr_2_node.id) # assert symbol_x_rhs_node.ast_node_type == :symbol # assert symbol_x_rhs_node.name == "x" # # TestHelpers.assert_node_typed_as(symbol_x_rhs_node, typed_nodes_map, %{ # type_kind: :literal, # value: 5 # }) # # TestHelpers.assert_node_typed_as(s_expr_2_node, typed_nodes_map, %{ # type_kind: :literal, # value: 5 # }) # # # Assert that 'y' in the environment (and thus its node if we were to look it up again) would be integer. # # The symbol_y_lhs_node itself might be typed as :any before the assignment's effect is fully "realized" on it. # # The critical part is that the environment passed forward contains y: :til_type_integer. # # The final environment is inspected in Typer.type_check, which can be manually verified for now. # # For this test, checking the type of the assignment expression (s_expr_2_node) and the RHS (symbol_x_rhs_node) is sufficient. # end # # test "types an if expression with same type in both branches, ambiguous condition" do # source = """ # (= cond_var some_ambiguous_symbol) # (if cond_var 1 2) # """ # # {if_node, typed_nodes_map} = TestHelpers.typecheck_and_get_nth_node(source, 1) # # assert if_node.ast_node_type == :s_expression # # expected_type_1 = %{type_kind: :literal, value: 1} # expected_type_2 = %{type_kind: :literal, value: 2} # # TestHelpers.assert_node_typed_as(if_node, typed_nodes_map, %{ # type_kind: :union, # types: Set.new([expected_type_1, expected_type_2]) # }) # end # # test "types an if expression with different types, ambiguous condition, resulting in a union" do # source = """ # (= cond_var some_ambiguous_symbol) # (if cond_var 1 'hello') # """ # # {if_node, typed_nodes_map} = TestHelpers.typecheck_and_get_nth_node(source, 1) # # assert if_node.ast_node_type == :s_expression # # expected_int_type = %{type_kind: :literal, value: 1} # expected_str_type = %{type_kind: :literal, value: "hello"} # # TestHelpers.assert_node_typed_as(if_node, typed_nodes_map, %{ # type_kind: :union, # types: Set.new([expected_int_type, expected_str_type]) # }) # end # # test "types an if expression with a missing else branch, ambiguous condition (union with nil type)" do # source = """ # (= cond_var some_ambiguous_symbol) # (if cond_var 1) # """ # # {if_node, typed_nodes_map} = TestHelpers.typecheck_and_get_nth_node(source, 1) # # assert if_node.ast_node_type == :s_expression # # expected_int_type = %{type_kind: :literal, value: 1} # expected_nil_type = %{type_kind: :literal, value: nil} # # TestHelpers.assert_node_typed_as(if_node, typed_nodes_map, %{ # type_kind: :union, # types: Set.new([expected_int_type, expected_nil_type]) # }) # end # # test "types an if expression where then branch is nil, missing else, condition true (results in nil type)" do # source = """ # (= x nil) # (if true x) # """ # # {if_node, typed_nodes_map} = TestHelpers.typecheck_and_get_nth_node(source, 1) # assert if_node.ast_node_type == :s_expression # expected_nil_type = %{type_kind: :literal, value: nil} # TestHelpers.assert_node_typed_as(if_node, typed_nodes_map, expected_nil_type) # end # # test "types an if expression where the condition is true, then branch is 1, missing else (results in `1` type)" do # source = """ # (= x 1) # (if true x) # """ # # {if_node, typed_nodes_map} = TestHelpers.typecheck_and_get_nth_node(source, 1) # assert if_node.ast_node_type == :s_expression # expected_type_of_1 = %{type_kind: :literal, value: 1} # TestHelpers.assert_node_typed_as(if_node, typed_nodes_map, expected_type_of_1) # end # # test "types an if expression where then and else are nil, condition true (results in nil type)" do # source = """ # (= x nil) # (if true x x) # """ # # {if_node, typed_nodes_map} = TestHelpers.typecheck_and_get_nth_node(source, 1) # assert if_node.ast_node_type == :s_expression # expected_nil_type = %{type_kind: :literal, value: nil} # TestHelpers.assert_node_typed_as(if_node, typed_nodes_map, expected_nil_type) # end # # test "if expression with statically false condition, missing else (results in nil type)" do # source = "(if false 123)" # {if_node, typed_nodes_map} = TestHelpers.typecheck_and_get_first_node(source) # assert if_node.ast_node_type == :s_expression # expected_nil_type = %{type_kind: :literal, value: nil} # TestHelpers.assert_node_typed_as(if_node, typed_nodes_map, expected_nil_type) # end # # test "if expression with statically false condition, with else branch (results in else branch type)" do # source = "(if false 123 'else_val')" # {if_node, typed_nodes_map} = TestHelpers.typecheck_and_get_first_node(source) # assert if_node.ast_node_type == :s_expression # expected_else_type = %{type_kind: :literal, value: "else_val"} # TestHelpers.assert_node_typed_as(if_node, typed_nodes_map, expected_else_type) # end # # test "if expression with truthy non-boolean literal condition type (integer)" do # source = """ # (if 123 'then' 'else') # """ # # Since 123 is truthy, the type of the if expression should be the type of 'then'. # {if_node, typed_nodes_map} = TestHelpers.typecheck_and_get_first_node(source) # assert if_node.ast_node_type == :s_expression # # expected_then_type = %{type_kind: :literal, value: "then"} # TestHelpers.assert_node_typed_as(if_node, typed_nodes_map, expected_then_type) # end # # test "if expression with truthy non-boolean symbol condition type (typed as integer)" do # source = """ # (= my_int_cond 123) # (if my_int_cond 'then' 'else') # """ # # my_int_cond is 123 (truthy), so the type of the if expression should be the type of 'then'. # {if_node, typed_nodes_map} = TestHelpers.typecheck_and_get_nth_node(source, 1) # assert if_node.ast_node_type == :s_expression # # expected_then_type = %{type_kind: :literal, value: "then"} # TestHelpers.assert_node_typed_as(if_node, typed_nodes_map, expected_then_type) # end # end # end