phasm/phasm/type3/entry.py
Johan B.W. de Vries 97b61e3ee1 Test generation framework with typing improvements
Prior to this PR, each type would have its own handwritten
test suite. The end result was that not all types were tested
for all situations.

This PR adds a framework based on a Markdown file, which
generates the basic tests for the types defined in json
files. These are auto generated and updated by the Makefile
before the test suite is run.

Also, a number of unsupported type combinations are now
supported.

Also, we now support negative literals.

Also, allocation calculation fixes for nested types.

Also, the test helpers can now properly import and export
typed variables such as bytes, static arrays and tuples. This
may come in handy when it comes to phasm platform wanting to
route data.

Also, adds better support for i8 type.

Also, started on a runtime.py, since there's quite some code
now that deals with compile time handling of WebAssembly stuff.

Also, minor improvement to the type constrains, namely we
better match 'tuple' literals with static array types.

Also, reduced spam when printing the type analysis results;
constraints that go back on the backlog are now no longer
printed one by one. It now also prints the end results of
the typing analysis.

Also, reorganized the big test_primitives test into type
classes.

Also, replaced pylint with ruff.
2023-11-15 12:52:23 +01:00

174 lines
5.9 KiB
Python

"""
Entry point to the type3 system
"""
from typing import Dict, List
from .. import codestyle, ourlang
from .constraints import (
ConstraintBase,
Error,
RequireTypeSubstitutes,
SameTypeConstraint,
SubstitutionMap,
)
from .constraintsgenerator import phasm_type3_generate_constraints
from .types import (
AppliedType3,
IntType3,
PlaceholderForType,
PrimitiveType3,
StructType3,
Type3,
Type3OrPlaceholder,
)
MAX_RESTACK_COUNT = 100
class Type3Exception(BaseException):
"""
Thrown when the Type3 system detects constraints that do not hold
"""
def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
constraint_list = phasm_type3_generate_constraints(inp)
assert constraint_list
placeholder_substitutes: Dict[PlaceholderForType, Type3] = {}
placeholder_id_map: Dict[int, str] = {}
error_list: List[Error] = []
for _ in range(MAX_RESTACK_COUNT):
if verbose:
print()
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
old_constraint_ids = {id(x) for x in constraint_list}
old_placeholder_substitutes_len = len(placeholder_substitutes)
back_on_todo_list_count = 0
new_constraint_list = []
for constraint in constraint_list:
check_result = constraint.check()
if check_result is None:
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Constraint checks out')
continue
if isinstance(check_result, dict):
placeholder_substitutes.update(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Constraint checks out, and gave us new information')
continue
if isinstance(check_result, Error):
error_list.append(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Got an error')
continue
if isinstance(check_result, RequireTypeSubstitutes):
new_constraint_list.append(constraint)
back_on_todo_list_count += 1
continue
if isinstance(check_result, list):
new_constraint_list.extend(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print(f'-> Resulted in {len(check_result)} new constraints')
continue
raise NotImplementedError(constraint, check_result)
if verbose and 0 < back_on_todo_list_count:
print(f'{back_on_todo_list_count} constraints skipped for now')
if not new_constraint_list:
constraint_list = new_constraint_list
break
# Infinite loop detection
new_constraint_ids = {id(x) for x in new_constraint_list}
new_placeholder_substitutes_len = len(placeholder_substitutes)
if old_constraint_ids == new_constraint_ids and old_placeholder_substitutes_len == new_placeholder_substitutes_len:
if error_list:
raise Type3Exception(error_list)
raise Exception('Cannot type this program - not enough information')
constraint_list = new_constraint_list
if verbose:
print()
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
if constraint_list:
raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations')
if error_list:
raise Type3Exception(error_list)
# FIXME: This doesn't work with e.g. `:: [a] -> a`, as the placeholder is inside a type
for plh, typ in placeholder_substitutes.items():
for expr in plh.update_on_substitution:
assert expr.type3 is plh
expr.type3 = typ
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None:
txt, fmt = constraint.human_readable()
act_fmt: Dict[str, str] = {}
for fmt_key, fmt_val in fmt.items():
if isinstance(fmt_val, ourlang.Expression):
fmt_val = codestyle.expression(fmt_val)
if isinstance(fmt_val, Type3) or isinstance(fmt_val, PlaceholderForType):
fmt_val = get_printable_type_name(fmt_val, placeholder_id_map)
if not isinstance(fmt_val, str):
fmt_val = repr(fmt_val)
act_fmt[fmt_key] = fmt_val
if constraint.comment is not None:
print('- ' + txt.format(**act_fmt).ljust(40) + '; ' + constraint.comment)
else:
print('- ' + txt.format(**act_fmt))
def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[int, str]) -> str:
if isinstance(inp, (PrimitiveType3, StructType3, IntType3, )):
return inp.name
if isinstance(inp, PlaceholderForType):
placeholder_id = id(inp)
if placeholder_id not in placeholder_id_map:
placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
return placeholder_id_map[placeholder_id]
if isinstance(inp, AppliedType3):
return (
get_printable_type_name(inp.base, placeholder_id_map)
+ ' ('
+ ') ('.join(get_printable_type_name(x, placeholder_id_map) for x in inp.args)
+ ')'
)
raise NotImplementedError(inp)
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
print('=== v type3 constraint_list v === ')
for psk, psv in placeholder_substitutes.items():
print_constraint(placeholder_id_map, SameTypeConstraint(psk, psv, comment='Deduced type'))
for constraint in constraint_list:
print_constraint(placeholder_id_map, constraint)
print('=== ^ type3 constraint_list ^ === ')