phasm/tests/integration/test_lang/test_second_order_functions.py
Johan B.W. de Vries 7df9d5af12 Removes the weird second step unify
It is now part of the normal constraints. Added a special
workaround for functions, since otherwise the output is a
bit redundant and quite confusing.

Also, constraints are now processed in order of complexity.
This does not affect type safety. It uses a bit more CPU.
But it makes the output that much easier to read.

Also, removes the weird FunctionInstance hack. Instead,
the more industry standard way of annotation the types
on the function call is used. As always, this requires some
hackyness for Subscriptable.

Also, adds a few comments to the type unification to help
with debugging.

Also, prints out the new constraints that are received.
2025-08-24 16:06:42 +02:00

254 lines
5.8 KiB
Python

import pytest
from phasm.type5.solver import Type5SolverException
from ..helpers import Suite
@pytest.mark.integration_test
def test_sof_in_code_0_arg():
code_py = """
def thirteen() -> i32:
return 13
def action(applicable: Callable[i32]) -> i32:
return applicable()
@exported
def testEntry() -> i32:
return action(thirteen)
"""
result = Suite(code_py).run_code()
assert 13 == result.returned_value
@pytest.mark.integration_test
def test_sof_in_code_1_arg():
code_py = """
def double(left: i32) -> i32:
return left * 2
def action(applicable: Callable[i32, i32], left: i32) -> i32:
return applicable(left)
@exported
def testEntry() -> i32:
return action(double, 13)
"""
result = Suite(code_py).run_code()
assert 26 == result.returned_value
@pytest.mark.integration_test
def test_sof_in_code_2_arg():
code_py = """
def add(left: i32, right: i32) -> i32:
return left + right
def action(applicable: Callable[i32, i32, i32], left: i32, right: i32) -> i32:
return applicable(left, right)
@exported
def testEntry() -> i32:
return action(add, 13, 14)
"""
result = Suite(code_py).run_code()
assert 27 == result.returned_value
@pytest.mark.integration_test
def test_sof_in_code_3_arg():
code_py = """
def add(left: i32, mid: i32, right: i32) -> i32:
return left + mid + right
def action(applicable: Callable[i32, i32, i32, i32], left: i32, mid: i32, right: i32) -> i32:
return applicable(left, mid, right)
@exported
def testEntry() -> i32:
return action(add, 13, 14, 15)
"""
result = Suite(code_py).run_code()
assert 42 == result.returned_value
@pytest.mark.integration_test
def test_sof_function_with_wrong_argument_type_use():
code_py = """
def double(left: i32) -> i32:
return left * 2
def action(applicable: Callable[i32, i32], left: f32) -> i32:
return applicable(left)
@exported
def testEntry() -> i32:
return action(double, 13.0)
"""
with pytest.raises(Type5SolverException, match='i32 ~ f32'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_function_with_wrong_argument_type_pass():
code_py = """
def double(left: f32) -> i32:
return truncate(left) * 2
def action(applicable: Callable[i32, i32], left: i32) -> i32:
return applicable(left)
@exported
def testEntry() -> i32:
return action(double, 13)
"""
with pytest.raises(Type5SolverException, match='i32 ~ f32'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_function_with_wrong_return_type_use():
code_py = """
def double(left: i32) -> i32:
return left * 2
def action(applicable: Callable[i32, i32], left: i32) -> f32:
return applicable(left)
@exported
def testEntry() -> f32:
return action(double, 13)
"""
with pytest.raises(Type5SolverException, match='i32 ~ f32'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_function_with_wrong_return_type_pass():
code_py = """
def double(left: i32) -> f32:
return convert(left) * 2.0
def action(applicable: Callable[i32, i32], left: i32) -> i32:
return applicable(left)
@exported
def testEntry() -> i32:
return action(double, 13)
"""
with pytest.raises(Type5SolverException, match='i32 ~ f32'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_not_enough_args_use():
code_py = """
def add(left: i32, right: i32) -> i32:
return left + right
def action(applicable: Callable[i32, i32, i32], left: i32) -> i32:
return applicable(left)
@exported
def testEntry() -> i32:
return action(add, 13)
"""
with pytest.raises(Type5SolverException, match='Not the same type'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_not_enough_args_pass():
code_py = """
def double(left: i32) -> i32:
return left * 2
def action(applicable: Callable[i32, i32, i32], left: i32, right: i32) -> i32:
return applicable(left, right)
@exported
def testEntry() -> i32:
return action(double, 13, 14)
"""
match = r'Callable\[i32, i32\] ~ i32'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_too_many_args_use_0():
code_py = """
def thirteen() -> i32:
return 13
def action(applicable: Callable[i32], left: i32) -> i32:
return applicable(left)
@exported
def testEntry() -> i32:
return action(thirteen, 13)
"""
match = r'\(\) ~ i32'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code(verbose=True)
@pytest.mark.integration_test
def test_sof_too_many_args_use_1():
code_py = """
def thirteen(x: i32) -> i32:
return x
def action(applicable: Callable[i32, i32], left: i32, right: i32) -> i32:
return applicable(left, right)
@exported
def testEntry() -> i32:
return action(thirteen, 13, 26)
"""
match = r'i32 ~ Callable\[i32, i32\]'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code(verbose=True)
@pytest.mark.integration_test
def test_sof_too_many_args_pass_0():
code_py = """
def double(left: i32) -> i32:
return left * 2
def action(applicable: Callable[i32], left: i32, right: i32) -> i32:
return applicable()
@exported
def testEntry() -> i32:
return action(double, 13, 14)
"""
match = r'\(\) ~ i32'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_sof_too_many_args_pass_1():
code_py = """
def double(left: i32, right: i32) -> i32:
return left * right
def action(applicable: Callable[i32, i32], left: i32, right: i32) -> i32:
return applicable(left)
@exported
def testEntry() -> i32:
return action(double, 13, 14)
"""
match = r'i32 ~ Callable\[i32, i32\]'
with pytest.raises(Type5SolverException, match=match):
Suite(code_py).run_code()