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.
174 lines
5.9 KiB
Python
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 ^ === ')
|