Cleanup to type5 solver
- Replaces weird CheckResult class with proper data classes. - When a constraint results in new constraints, those are treated first - Minor readability change to function return type - Code cleanup - TODO cleanup - Typo fixes
This commit is contained in:
parent
71691d68e9
commit
d1854d7a38
3
TODO.md
3
TODO.md
@ -24,7 +24,4 @@
|
|||||||
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm
|
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm
|
||||||
|
|
||||||
- Implement type class 'inheritance'
|
- Implement type class 'inheritance'
|
||||||
- Remove FunctionInstance, replace with a substitutions dict
|
|
||||||
- See phft2 in fromast.py:expression_function_call
|
|
||||||
- Move unify into the typeconstraints (or other way around) - it's done on two levels now (partly in solver)
|
|
||||||
- Rework type classes - already started on a separate dir for those, but quite a few things are still in other places.
|
- Rework type classes - already started on a separate dir for those, but quite a few things are still in other places.
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from .typeexpr import TypeExpr, TypeVariable, instantiate
|
|||||||
|
|
||||||
class TypeConstraint:
|
class TypeConstraint:
|
||||||
"""
|
"""
|
||||||
Base class for type contraints
|
Base class for type constraints
|
||||||
"""
|
"""
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
|||||||
@ -75,6 +75,33 @@ class Context:
|
|||||||
assert tvar not in self.ptst_update
|
assert tvar not in self.ptst_update
|
||||||
self.ptst_update[tvar] = (arg, orig_var)
|
self.ptst_update[tvar] = (arg, orig_var)
|
||||||
|
|
||||||
|
class Success:
|
||||||
|
"""
|
||||||
|
The contsraint checks out.
|
||||||
|
|
||||||
|
Nothing new was learned and nothing new needs to be checked.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
||||||
|
return '(ok)'
|
||||||
|
|
||||||
|
class SkipForNow:
|
||||||
|
"""
|
||||||
|
Not enough information to resolve this constraint
|
||||||
|
"""
|
||||||
|
|
||||||
|
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
||||||
|
return '(skip for now)'
|
||||||
|
|
||||||
|
class ConstraintList(list['ConstraintBase']):
|
||||||
|
"""
|
||||||
|
A new list of constraints.
|
||||||
|
|
||||||
|
Sometimes, checking one constraint means you get a list of new contraints.
|
||||||
|
"""
|
||||||
|
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
||||||
|
return f'(got {len(self)} new constraints)'
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class Failure:
|
class Failure:
|
||||||
"""
|
"""
|
||||||
@ -82,52 +109,38 @@ class Failure:
|
|||||||
"""
|
"""
|
||||||
msg: str
|
msg: str
|
||||||
|
|
||||||
|
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
||||||
|
return f'ERR: {self.msg}'
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class ReplaceVariable:
|
class ReplaceVariable:
|
||||||
|
"""
|
||||||
|
A variable should be replaced.
|
||||||
|
|
||||||
|
Either by another variable or by a (concrete) type.
|
||||||
|
"""
|
||||||
var: TypeVariable
|
var: TypeVariable
|
||||||
typ: TypeExpr
|
typ: TypeExpr
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class CheckResult:
|
|
||||||
# TODO: Refactor this, don't think we use most of the variants
|
|
||||||
_: dataclasses.KW_ONLY
|
|
||||||
done: bool = True
|
|
||||||
replace: ReplaceVariable | None = None
|
|
||||||
new_constraints: list[ConstraintBase] = dataclasses.field(default_factory=list)
|
|
||||||
failures: list[Failure] = dataclasses.field(default_factory=list)
|
|
||||||
|
|
||||||
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
||||||
if not self.done and not self.replace and not self.new_constraints and not self.failures:
|
return f'{{{self.var.name} := {type_namer(self.typ)}}}'
|
||||||
return '(skip for now)'
|
|
||||||
|
|
||||||
if self.done and not self.replace and not self.new_constraints and not self.failures:
|
CheckResult: TypeAlias = Success | SkipForNow | ConstraintList | Failure | ReplaceVariable
|
||||||
return '(ok)'
|
|
||||||
|
|
||||||
if self.done and self.replace and not self.new_constraints and not self.failures:
|
|
||||||
return f'{{{self.replace.var.name} := {type_namer(self.replace.typ)}}}'
|
|
||||||
|
|
||||||
if self.done and not self.replace and self.new_constraints and not self.failures:
|
|
||||||
return f'(got {len(self.new_constraints)} new constraints)'
|
|
||||||
|
|
||||||
if self.done and not self.replace and not self.new_constraints and self.failures:
|
|
||||||
return 'ERR: ' + '; '.join(x.msg for x in self.failures)
|
|
||||||
|
|
||||||
return f'{self.done} {self.replace} {self.new_constraints} {self.failures}'
|
|
||||||
|
|
||||||
def skip_for_now() -> CheckResult:
|
|
||||||
return CheckResult(done=False)
|
|
||||||
|
|
||||||
def replace(var: TypeVariable, typ: TypeExpr) -> CheckResult:
|
|
||||||
return CheckResult(replace=ReplaceVariable(var, typ))
|
|
||||||
|
|
||||||
def new_constraints(lst: Iterable[ConstraintBase]) -> CheckResult:
|
|
||||||
return CheckResult(new_constraints=list(lst))
|
|
||||||
|
|
||||||
def ok() -> CheckResult:
|
def ok() -> CheckResult:
|
||||||
return CheckResult(done=True)
|
return Success()
|
||||||
|
|
||||||
|
def skip_for_now() -> CheckResult:
|
||||||
|
return SkipForNow()
|
||||||
|
|
||||||
|
def new_constraints(lst: Iterable[ConstraintBase]) -> CheckResult:
|
||||||
|
return ConstraintList(lst)
|
||||||
|
|
||||||
def fail(msg: str) -> CheckResult:
|
def fail(msg: str) -> CheckResult:
|
||||||
return CheckResult(failures=[Failure(msg)])
|
return Failure(msg)
|
||||||
|
|
||||||
|
def replace(var: TypeVariable, typ: TypeExpr) -> CheckResult:
|
||||||
|
return ReplaceVariable(var, typ)
|
||||||
|
|
||||||
class ConstraintBase:
|
class ConstraintBase:
|
||||||
__slots__ = ("ctx", "sourceref", )
|
__slots__ = ("ctx", "sourceref", )
|
||||||
@ -399,7 +412,7 @@ class CanBeSubscriptedConstraint(ConstraintBase):
|
|||||||
|
|
||||||
def check(self) -> CheckResult:
|
def check(self) -> CheckResult:
|
||||||
if not is_concrete(self.container_type5):
|
if not is_concrete(self.container_type5):
|
||||||
return CheckResult(done=False)
|
return skip_for_now()
|
||||||
|
|
||||||
tp_args = self.ctx.build.type5_is_tuple(self.container_type5)
|
tp_args = self.ctx.build.type5_is_tuple(self.container_type5)
|
||||||
if tp_args is not None:
|
if tp_args is not None:
|
||||||
@ -462,7 +475,7 @@ class CanAccessStructMemberConstraint(ConstraintBase):
|
|||||||
|
|
||||||
def check(self) -> CheckResult:
|
def check(self) -> CheckResult:
|
||||||
if not is_concrete(self.struct_type5):
|
if not is_concrete(self.struct_type5):
|
||||||
return CheckResult(done=False)
|
return skip_for_now()
|
||||||
|
|
||||||
st_args = self.ctx.build.type5_is_struct(self.struct_type5)
|
st_args = self.ctx.build.type5_is_struct(self.struct_type5)
|
||||||
if st_args is None:
|
if st_args is None:
|
||||||
@ -514,11 +527,11 @@ class FromTupleConstraint(ConstraintBase):
|
|||||||
|
|
||||||
def check(self) -> CheckResult:
|
def check(self) -> CheckResult:
|
||||||
if not is_concrete(self.ret_type5):
|
if not is_concrete(self.ret_type5):
|
||||||
return CheckResult(done=False)
|
return skip_for_now()
|
||||||
|
|
||||||
da_arg = self.ctx.build.type5_is_dynamic_array(self.ret_type5)
|
da_arg = self.ctx.build.type5_is_dynamic_array(self.ret_type5)
|
||||||
if da_arg is not None:
|
if da_arg is not None:
|
||||||
return CheckResult(new_constraints=[
|
return new_constraints([
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, da_arg, x)
|
UnifyTypesConstraint(self.ctx, self.sourceref, da_arg, x)
|
||||||
for x in self.member_type5_list
|
for x in self.member_type5_list
|
||||||
])
|
])
|
||||||
@ -529,7 +542,7 @@ class FromTupleConstraint(ConstraintBase):
|
|||||||
if sa_len != len(self.member_type5_list):
|
if sa_len != len(self.member_type5_list):
|
||||||
return fail('Tuple element count mismatch')
|
return fail('Tuple element count mismatch')
|
||||||
|
|
||||||
return CheckResult(new_constraints=[
|
return new_constraints([
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, sa_typ, x)
|
UnifyTypesConstraint(self.ctx, self.sourceref, sa_typ, x)
|
||||||
for x in self.member_type5_list
|
for x in self.member_type5_list
|
||||||
])
|
])
|
||||||
@ -539,7 +552,7 @@ class FromTupleConstraint(ConstraintBase):
|
|||||||
if len(tp_args) != len(self.member_type5_list):
|
if len(tp_args) != len(self.member_type5_list):
|
||||||
return fail('Tuple element count mismatch')
|
return fail('Tuple element count mismatch')
|
||||||
|
|
||||||
return CheckResult(new_constraints=[
|
return new_constraints([
|
||||||
UnifyTypesConstraint(self.ctx, self.sourceref, act_typ, exp_typ)
|
UnifyTypesConstraint(self.ctx, self.sourceref, act_typ, exp_typ)
|
||||||
for act_typ, exp_typ in zip(tp_args, self.member_type5_list, strict=True)
|
for act_typ, exp_typ in zip(tp_args, self.member_type5_list, strict=True)
|
||||||
])
|
])
|
||||||
|
|||||||
@ -241,7 +241,7 @@ def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement
|
|||||||
type5 = fun.type5.expr if isinstance(fun.type5, ConstrainedExpr) else fun.type5
|
type5 = fun.type5.expr if isinstance(fun.type5, ConstrainedExpr) else fun.type5
|
||||||
|
|
||||||
yield from expression(ctx, inp.value, phft)
|
yield from expression(ctx, inp.value, phft)
|
||||||
yield UnifyTypesConstraint(ctx, inp.sourceref, type5, phft, prefix=f'{fun.name} returns')
|
yield UnifyTypesConstraint(ctx, inp.sourceref, type5, phft, prefix=f'{fun.name}(...)')
|
||||||
|
|
||||||
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
|
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
|
||||||
test_phft = ctx.make_placeholder(inp.test)
|
test_phft = ctx.make_placeholder(inp.test)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from ..ourlang import Module
|
from ..ourlang import Module
|
||||||
from .constraints import ConstraintBase, Context
|
from .constraints import ConstraintBase, ConstraintList, Context, Failure, ReplaceVariable, SkipForNow, Success
|
||||||
from .fromast import phasm_type5_generate_constraints
|
from .fromast import phasm_type5_generate_constraints
|
||||||
from .typeexpr import TypeExpr, TypeVariable, is_concrete, replace_variable
|
from .typeexpr import TypeExpr, TypeVariable, is_concrete, replace_variable
|
||||||
|
|
||||||
@ -30,58 +30,68 @@ def phasm_type5(inp: Module[Any], verbose: bool = False) -> None:
|
|||||||
print("Validating")
|
print("Validating")
|
||||||
|
|
||||||
new_constraint_list: list[ConstraintBase] = []
|
new_constraint_list: list[ConstraintBase] = []
|
||||||
for constraint in sorted(constraint_list, key=lambda x: x.complexity()):
|
|
||||||
|
# Iterate using a while and pop since on ReplaceVariable
|
||||||
|
# we want to iterate over the list as well, and since on
|
||||||
|
# ConstraintList we want to treat those first.
|
||||||
|
remaining_constraint_list = sorted(constraint_list, key=lambda x: x.complexity())
|
||||||
|
while remaining_constraint_list:
|
||||||
|
constraint = remaining_constraint_list.pop(0)
|
||||||
result = constraint.check()
|
result = constraint.check()
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f"{constraint.sourceref!s} {constraint!s}")
|
print(f"{constraint.sourceref!s} {constraint!s}")
|
||||||
print(f"{constraint.sourceref!s} => {result.to_str(inp.build.type5_name)}")
|
print(f"{constraint.sourceref!s} => {result.to_str(inp.build.type5_name)}")
|
||||||
|
|
||||||
if not result:
|
match result:
|
||||||
# None or empty list
|
case Success():
|
||||||
# Means it checks out and we don't need do anything
|
# This constraint was valid
|
||||||
continue
|
continue
|
||||||
|
case SkipForNow():
|
||||||
|
# We have to check later
|
||||||
|
new_constraint_list.append(constraint)
|
||||||
|
continue
|
||||||
|
case ConstraintList(items):
|
||||||
|
# This constraint was valid, but we have new once
|
||||||
|
# Do this as the first next items, so when users are reading the
|
||||||
|
# solver output they don't need to context switch.
|
||||||
|
remaining_constraint_list = items + remaining_constraint_list
|
||||||
|
|
||||||
if result.replace is not None:
|
if verbose:
|
||||||
action_var = result.replace.var
|
for new_const in items:
|
||||||
assert action_var not in placeholder_types # When does this happen?
|
print(f"{constraint.sourceref!s} => + {new_const!s}")
|
||||||
|
|
||||||
action_typ: TypeExpr = result.replace.typ
|
continue
|
||||||
assert not isinstance(action_typ, TypeVariable) or action_typ not in placeholder_types # When does this happen?
|
case Failure(msg):
|
||||||
|
error_list.append((str(constraint.sourceref), str(constraint), msg, ))
|
||||||
|
continue
|
||||||
|
case ReplaceVariable(action_var, action_typ):
|
||||||
|
assert action_var not in placeholder_types # When does this happen?
|
||||||
|
assert not isinstance(action_typ, TypeVariable) or action_typ not in placeholder_types # When does this happen?
|
||||||
|
assert action_var != action_typ # When does this happen?
|
||||||
|
|
||||||
assert action_var != action_typ # When does this happen?
|
# Ensure all existing found types are updated
|
||||||
|
# if they have this variable somewhere inside them.
|
||||||
|
placeholder_types = {
|
||||||
|
k: replace_variable(v, action_var, action_typ)
|
||||||
|
for k, v in placeholder_types.items()
|
||||||
|
}
|
||||||
|
|
||||||
# Ensure all existing found types are updated
|
# Add the new variable to the registry
|
||||||
placeholder_types = {
|
placeholder_types[action_var] = action_typ
|
||||||
k: replace_variable(v, action_var, action_typ)
|
|
||||||
for k, v in placeholder_types.items()
|
|
||||||
}
|
|
||||||
placeholder_types[action_var] = action_typ
|
|
||||||
|
|
||||||
for oth_const in new_constraint_list + constraint_list:
|
# Also update all constraints that may refer to this variable
|
||||||
if oth_const is constraint and result.done:
|
# that they now have more detailed information.
|
||||||
continue
|
for oth_const in new_constraint_list + remaining_constraint_list:
|
||||||
|
old_str = str(oth_const)
|
||||||
|
oth_const.replace_variable(action_var, action_typ)
|
||||||
|
new_str = str(oth_const)
|
||||||
|
|
||||||
old_str = str(oth_const)
|
if verbose and old_str != new_str:
|
||||||
oth_const.replace_variable(action_var, action_typ)
|
print(f"{oth_const.sourceref!s} => - {old_str!s}")
|
||||||
new_str = str(oth_const)
|
print(f"{oth_const.sourceref!s} => + {new_str!s}")
|
||||||
|
|
||||||
if verbose and old_str != new_str:
|
continue
|
||||||
print(f"{oth_const.sourceref!s} => - {old_str!s}")
|
|
||||||
print(f"{oth_const.sourceref!s} => + {new_str!s}")
|
|
||||||
|
|
||||||
for failure in result.failures:
|
|
||||||
error_list.append((str(constraint.sourceref), str(constraint), failure.msg, ))
|
|
||||||
|
|
||||||
new_constraint_list.extend(result.new_constraints)
|
|
||||||
if verbose:
|
|
||||||
for new_const in result.new_constraints:
|
|
||||||
print(f"{oth_const.sourceref!s} => + {new_const!s}")
|
|
||||||
|
|
||||||
if result.done:
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_constraint_list.append(constraint)
|
|
||||||
|
|
||||||
if error_list:
|
if error_list:
|
||||||
raise Type5SolverException(error_list)
|
raise Type5SolverException(error_list)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user