Compare commits
1 Commits
implement-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1854d7a38 |
3
TODO.md
3
TODO.md
@ -24,7 +24,4 @@
|
||||
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm
|
||||
|
||||
- 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.
|
||||
|
||||
@ -10,7 +10,7 @@ from .typeexpr import TypeExpr, TypeVariable, instantiate
|
||||
|
||||
class TypeConstraint:
|
||||
"""
|
||||
Base class for type contraints
|
||||
Base class for type constraints
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@ -75,6 +75,33 @@ class Context:
|
||||
assert tvar not in self.ptst_update
|
||||
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
|
||||
class Failure:
|
||||
"""
|
||||
@ -82,52 +109,38 @@ class Failure:
|
||||
"""
|
||||
msg: str
|
||||
|
||||
def to_str(self, type_namer: Callable[[TypeExpr], str]) -> str:
|
||||
return f'ERR: {self.msg}'
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ReplaceVariable:
|
||||
"""
|
||||
A variable should be replaced.
|
||||
|
||||
Either by another variable or by a (concrete) type.
|
||||
"""
|
||||
var: TypeVariable
|
||||
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:
|
||||
if not self.done and not self.replace and not self.new_constraints and not self.failures:
|
||||
return '(skip for now)'
|
||||
return f'{{{self.var.name} := {type_namer(self.typ)}}}'
|
||||
|
||||
if self.done and not self.replace and not self.new_constraints and not self.failures:
|
||||
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))
|
||||
CheckResult: TypeAlias = Success | SkipForNow | ConstraintList | Failure | ReplaceVariable
|
||||
|
||||
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:
|
||||
return CheckResult(failures=[Failure(msg)])
|
||||
return Failure(msg)
|
||||
|
||||
def replace(var: TypeVariable, typ: TypeExpr) -> CheckResult:
|
||||
return ReplaceVariable(var, typ)
|
||||
|
||||
class ConstraintBase:
|
||||
__slots__ = ("ctx", "sourceref", )
|
||||
@ -399,7 +412,7 @@ class CanBeSubscriptedConstraint(ConstraintBase):
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
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)
|
||||
if tp_args is not None:
|
||||
@ -462,7 +475,7 @@ class CanAccessStructMemberConstraint(ConstraintBase):
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
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)
|
||||
if st_args is None:
|
||||
@ -514,11 +527,11 @@ class FromTupleConstraint(ConstraintBase):
|
||||
|
||||
def check(self) -> CheckResult:
|
||||
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)
|
||||
if da_arg is not None:
|
||||
return CheckResult(new_constraints=[
|
||||
return new_constraints([
|
||||
UnifyTypesConstraint(self.ctx, self.sourceref, da_arg, x)
|
||||
for x in self.member_type5_list
|
||||
])
|
||||
@ -529,7 +542,7 @@ class FromTupleConstraint(ConstraintBase):
|
||||
if sa_len != len(self.member_type5_list):
|
||||
return fail('Tuple element count mismatch')
|
||||
|
||||
return CheckResult(new_constraints=[
|
||||
return new_constraints([
|
||||
UnifyTypesConstraint(self.ctx, self.sourceref, sa_typ, x)
|
||||
for x in self.member_type5_list
|
||||
])
|
||||
@ -539,7 +552,7 @@ class FromTupleConstraint(ConstraintBase):
|
||||
if len(tp_args) != len(self.member_type5_list):
|
||||
return fail('Tuple element count mismatch')
|
||||
|
||||
return CheckResult(new_constraints=[
|
||||
return new_constraints([
|
||||
UnifyTypesConstraint(self.ctx, self.sourceref, act_typ, exp_typ)
|
||||
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
|
||||
|
||||
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:
|
||||
test_phft = ctx.make_placeholder(inp.test)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from typing import Any
|
||||
|
||||
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 .typeexpr import TypeExpr, TypeVariable, is_concrete, replace_variable
|
||||
|
||||
@ -30,58 +30,68 @@ def phasm_type5(inp: Module[Any], verbose: bool = False) -> None:
|
||||
print("Validating")
|
||||
|
||||
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()
|
||||
|
||||
if verbose:
|
||||
print(f"{constraint.sourceref!s} {constraint!s}")
|
||||
print(f"{constraint.sourceref!s} => {result.to_str(inp.build.type5_name)}")
|
||||
|
||||
if not result:
|
||||
# None or empty list
|
||||
# Means it checks out and we don't need do anything
|
||||
continue
|
||||
match result:
|
||||
case Success():
|
||||
# This constraint was valid
|
||||
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:
|
||||
action_var = result.replace.var
|
||||
assert action_var not in placeholder_types # When does this happen?
|
||||
if verbose:
|
||||
for new_const in items:
|
||||
print(f"{constraint.sourceref!s} => + {new_const!s}")
|
||||
|
||||
action_typ: TypeExpr = result.replace.typ
|
||||
assert not isinstance(action_typ, TypeVariable) or action_typ not in placeholder_types # When does this happen?
|
||||
continue
|
||||
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
|
||||
placeholder_types = {
|
||||
k: replace_variable(v, action_var, action_typ)
|
||||
for k, v in placeholder_types.items()
|
||||
}
|
||||
placeholder_types[action_var] = action_typ
|
||||
# Add the new variable to the registry
|
||||
placeholder_types[action_var] = action_typ
|
||||
|
||||
for oth_const in new_constraint_list + constraint_list:
|
||||
if oth_const is constraint and result.done:
|
||||
continue
|
||||
# Also update all constraints that may refer to this variable
|
||||
# that they now have more detailed information.
|
||||
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)
|
||||
oth_const.replace_variable(action_var, action_typ)
|
||||
new_str = str(oth_const)
|
||||
if verbose and old_str != new_str:
|
||||
print(f"{oth_const.sourceref!s} => - {old_str!s}")
|
||||
print(f"{oth_const.sourceref!s} => + {new_str!s}")
|
||||
|
||||
if verbose and old_str != new_str:
|
||||
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)
|
||||
continue
|
||||
|
||||
if error_list:
|
||||
raise Type5SolverException(error_list)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user