Fixes
- Moved the three ConstraintBase functions into one, as it was giving trouble calculating everything - Gave each constraint the option to have a comment - SameTypeConstraint now can have multiple arguments - Redid the infinite loop detection - Implemented some more basic operators - Redid the division by zero for floats, it wasn't giving the right results. - Also postponed the updating the AST type3 until afterwards - Various linting fixes
This commit is contained in:
parent
a838035e1a
commit
05e7c356ea
@ -3,7 +3,7 @@ This module generates source code based on the parsed AST
|
|||||||
|
|
||||||
It's intented to be a "any color, as long as it's black" kind of renderer
|
It's intented to be a "any color, as long as it's black" kind of renderer
|
||||||
"""
|
"""
|
||||||
from typing import Generator, Optional
|
from typing import Generator
|
||||||
|
|
||||||
from . import ourlang
|
from . import ourlang
|
||||||
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
|
from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder
|
||||||
|
|||||||
@ -410,7 +410,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
|
|||||||
raise NotImplementedError(expression, inp, inp.iter.type)
|
raise NotImplementedError(expression, inp, inp.iter.type)
|
||||||
|
|
||||||
wgn.add_statement('nop', comment='acu :: u8')
|
wgn.add_statement('nop', comment='acu :: u8')
|
||||||
acu_var = wgn.temp_var_u8(f'fold_{codestyle.type_(inp.type)}_acu')
|
acu_var = wgn.temp_var_u8(f'fold_{codestyle.type3(inp.type3)}_acu')
|
||||||
wgn.add_statement('nop', comment='adr :: bytes*')
|
wgn.add_statement('nop', comment='adr :: bytes*')
|
||||||
adr_var = wgn.temp_var_i32('fold_i32_adr')
|
adr_var = wgn.temp_var_i32('fold_i32_adr')
|
||||||
wgn.add_statement('nop', comment='len :: i32')
|
wgn.add_statement('nop', comment='len :: i32')
|
||||||
@ -430,7 +430,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
|
|||||||
wgn.local.set(len_var)
|
wgn.local.set(len_var)
|
||||||
|
|
||||||
wgn.add_statement('nop', comment='i = 0')
|
wgn.add_statement('nop', comment='i = 0')
|
||||||
idx_var = wgn.temp_var_i32(f'fold_{codestyle.type_(inp.type)}_idx')
|
idx_var = wgn.temp_var_i32(f'fold_{codestyle.type3(inp.type3)}_idx')
|
||||||
wgn.i32.const(0)
|
wgn.i32.const(0)
|
||||||
wgn.local.set(idx_var)
|
wgn.local.set(idx_var)
|
||||||
|
|
||||||
@ -765,7 +765,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstruc
|
|||||||
# Return the allocated address
|
# Return the allocated address
|
||||||
wgn.local.get(tmp_var)
|
wgn.local.get(tmp_var)
|
||||||
|
|
||||||
def _calculate_alloc_size(type3: Union[type3types.StructType3, type3types.Type3]) -> int:
|
def _calculate_alloc_size(typ: Union[type3types.StructType3, type3types.Type3]) -> int:
|
||||||
return 0 # FIXME: Stub
|
return 0 # FIXME: Stub
|
||||||
|
|
||||||
def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int:
|
def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int:
|
||||||
|
|||||||
@ -3,13 +3,19 @@ This module contains possible constraints generated based on the AST
|
|||||||
|
|
||||||
These need to be resolved before the program can be compiled.
|
These need to be resolved before the program can be compiled.
|
||||||
"""
|
"""
|
||||||
from typing import Dict, Tuple, Union
|
from typing import Dict, Optional, List, Tuple, Union
|
||||||
|
|
||||||
from .. import ourlang
|
from .. import ourlang
|
||||||
|
|
||||||
from . import types
|
from . import types
|
||||||
|
|
||||||
class Error:
|
class Error:
|
||||||
|
"""
|
||||||
|
An error returned by the check functions for a contraint
|
||||||
|
|
||||||
|
This means the programmer has to make some kind of chance to the
|
||||||
|
typing of their program before the compiler can do its thing.
|
||||||
|
"""
|
||||||
def __init__(self, msg: str) -> None:
|
def __init__(self, msg: str) -> None:
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
|
||||||
@ -17,12 +23,18 @@ class Error:
|
|||||||
return f'Error({repr(self.msg)})'
|
return f'Error({repr(self.msg)})'
|
||||||
|
|
||||||
class RequireTypeSubstitutes:
|
class RequireTypeSubstitutes:
|
||||||
pass
|
"""
|
||||||
|
Returned by the check function for a contraint if they do not have all
|
||||||
|
their types substituted yet.
|
||||||
|
|
||||||
CheckResult = Union[None, Error, RequireTypeSubstitutes]
|
Hopefully, another constraint will give the right information about the
|
||||||
|
typing of the program, so this constraint can be updated.
|
||||||
|
"""
|
||||||
|
|
||||||
SubstitutionMap = Dict[types.PlaceholderForType, types.Type3]
|
SubstitutionMap = Dict[types.PlaceholderForType, types.Type3]
|
||||||
|
|
||||||
|
CheckResult = Union[None, SubstitutionMap, Error, RequireTypeSubstitutes]
|
||||||
|
|
||||||
HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, types.PlaceholderForType]]]
|
HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, types.PlaceholderForType]]]
|
||||||
|
|
||||||
class Context:
|
class Context:
|
||||||
@ -36,28 +48,39 @@ class ConstraintBase:
|
|||||||
"""
|
"""
|
||||||
Base class for constraints
|
Base class for constraints
|
||||||
"""
|
"""
|
||||||
__slots__ = ()
|
__slots__ = ('comment', )
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
comment: Optional[str]
|
||||||
"""
|
"""
|
||||||
Checks if the constraint hold, returning an error if it doesn't
|
A comment to help the programmer with debugging the types in their program
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, comment: Optional[str] = None) -> None:
|
||||||
|
self.comment = comment
|
||||||
|
|
||||||
|
def check(self, smap: SubstitutionMap) -> CheckResult:
|
||||||
|
"""
|
||||||
|
Checks if the constraint hold
|
||||||
|
|
||||||
|
smap will contain a mapping from placeholders to types, for
|
||||||
|
placeholders discovered from either other constraints or from
|
||||||
|
earlier calls to check in this constraint.
|
||||||
|
|
||||||
|
This function can return an error, if the constraint does not hold,
|
||||||
|
which indicates an error in the typing of the input program.
|
||||||
|
|
||||||
|
This function can return RequireTypeSubstitutes(), if we cannot deduce
|
||||||
|
all the types yet.
|
||||||
|
|
||||||
|
This function can return a SubstitutionMap, if during the evaluation
|
||||||
|
of the contraint we discovered new types. In this case, the constraint
|
||||||
|
is expected to hold.
|
||||||
|
|
||||||
|
This function can return None, if the constraint holds, but no new
|
||||||
|
information was deduced from evaluating this constraint.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_new_placeholder_substitutes(self) -> SubstitutionMap:
|
|
||||||
"""
|
|
||||||
Returns any new placeholders that can be substituted for actual types
|
|
||||||
"""
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def substitute_placeholders(self, smap: SubstitutionMap) -> None:
|
|
||||||
"""
|
|
||||||
Called with type substitutes, so you can update any placeholders
|
|
||||||
you may have with the known types. Note that this does not guarantee
|
|
||||||
that all types are known, you may still have some placeholders left.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError(self, self.substitute_placeholders)
|
|
||||||
|
|
||||||
def human_readable(self) -> HumanReadableRet:
|
def human_readable(self) -> HumanReadableRet:
|
||||||
"""
|
"""
|
||||||
Returns a more human readable form of this constraint
|
Returns a more human readable form of this constraint
|
||||||
@ -66,71 +89,61 @@ class ConstraintBase:
|
|||||||
|
|
||||||
class SameTypeConstraint(ConstraintBase):
|
class SameTypeConstraint(ConstraintBase):
|
||||||
"""
|
"""
|
||||||
Verifies that an expression has an expected type
|
Verifies that a number of types all are the same type
|
||||||
"""
|
"""
|
||||||
__slots__ = ('expected', 'actual', 'message', )
|
__slots__ = ('type_list', )
|
||||||
|
|
||||||
expected: types.Type3OrPlaceholder
|
type_list: List[types.Type3OrPlaceholder]
|
||||||
actual: types.Type3OrPlaceholder
|
|
||||||
message: str
|
|
||||||
|
|
||||||
def __init__(self, expected: types.Type3OrPlaceholder, actual: types.Type3OrPlaceholder, message: str) -> None:
|
def __init__(self, *type_list: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
|
||||||
self.expected = expected
|
super().__init__(comment=comment)
|
||||||
self.actual = actual
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
assert len(type_list) > 1
|
||||||
if isinstance(self.expected, types.PlaceholderForType) or isinstance(self.actual, types.PlaceholderForType):
|
self.type_list = [*type_list]
|
||||||
|
|
||||||
|
def check(self, smap: SubstitutionMap) -> CheckResult:
|
||||||
|
known_types = []
|
||||||
|
placeholders = []
|
||||||
|
for typ in self.type_list:
|
||||||
|
if isinstance(typ, types.Type3):
|
||||||
|
known_types.append(typ)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if typ in smap:
|
||||||
|
known_types.append(smap[typ])
|
||||||
|
continue
|
||||||
|
|
||||||
|
placeholders.append(typ)
|
||||||
|
|
||||||
|
if not known_types:
|
||||||
return RequireTypeSubstitutes()
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
if self.expected is self.actual:
|
first_type = known_types[0]
|
||||||
|
for typ in known_types[1:]:
|
||||||
|
if typ is not first_type:
|
||||||
|
return Error(f'{typ:s} must be {first_type:s} instead')
|
||||||
|
|
||||||
|
if not placeholders:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return Error(f'{self.expected:s} must be {self.actual:s} instead')
|
return {
|
||||||
|
typ: first_type
|
||||||
def get_new_placeholder_substitutes(self) -> SubstitutionMap:
|
for typ in placeholders
|
||||||
result: SubstitutionMap = {}
|
|
||||||
|
|
||||||
if isinstance(self.expected, types.Type3) and isinstance(self.actual, types.PlaceholderForType):
|
|
||||||
result = {
|
|
||||||
self.actual: self.expected
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.actual.get_substituted(self.expected)
|
|
||||||
self.actual = self.expected
|
|
||||||
|
|
||||||
|
|
||||||
if isinstance(self.actual, types.Type3) and isinstance(self.expected, types.PlaceholderForType):
|
|
||||||
result = {
|
|
||||||
self.expected: self.actual
|
|
||||||
}
|
|
||||||
|
|
||||||
self.expected.get_substituted(self.actual)
|
|
||||||
self.expected = self.actual
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def substitute_placeholders(self, smap: SubstitutionMap) -> None:
|
|
||||||
if isinstance(self.expected, types.PlaceholderForType) and self.expected in smap: # FIXME: Check recursive?
|
|
||||||
self.expected.get_substituted(smap[self.expected])
|
|
||||||
self.expected = smap[self.expected]
|
|
||||||
|
|
||||||
if isinstance(self.actual, types.PlaceholderForType) and self.actual in smap: # FIXME: Check recursive?
|
|
||||||
self.actual.get_substituted(smap[self.actual])
|
|
||||||
self.actual = smap[self.actual]
|
|
||||||
|
|
||||||
def human_readable(self) -> HumanReadableRet:
|
def human_readable(self) -> HumanReadableRet:
|
||||||
return (
|
return (
|
||||||
'{expected} == {actual}',
|
' == '.join('{t' + str(idx) + '}' for idx in range(len(self.type_list))),
|
||||||
{
|
{
|
||||||
'expected': self.expected,
|
't' + str(idx): typ
|
||||||
'actual': self.actual,
|
for idx, typ in enumerate(self.type_list)
|
||||||
'comment': self.message,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'SameTypeConstraint({repr(self.expected)}, {repr(self.actual)}, {repr(self.message)})'
|
args = ', '.join(repr(x) for x in self.type_list)
|
||||||
|
|
||||||
|
return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
|
||||||
|
|
||||||
class MustImplementTypeClassConstraint(ConstraintBase):
|
class MustImplementTypeClassConstraint(ConstraintBase):
|
||||||
"""
|
"""
|
||||||
@ -141,23 +154,31 @@ class MustImplementTypeClassConstraint(ConstraintBase):
|
|||||||
type_class3: str
|
type_class3: str
|
||||||
type3: types.Type3OrPlaceholder
|
type3: types.Type3OrPlaceholder
|
||||||
|
|
||||||
def __init__(self, type_class3: str, type3: types.Type3OrPlaceholder) -> None:
|
def __init__(self, type_class3: str, type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
|
||||||
|
super().__init__(comment=comment)
|
||||||
|
|
||||||
self.type_class3 = type_class3
|
self.type_class3 = type_class3
|
||||||
self.type3 = type3
|
self.type3 = type3
|
||||||
|
|
||||||
def substitute_placeholders(self, smap: SubstitutionMap) -> None:
|
def check(self, smap: SubstitutionMap) -> CheckResult:
|
||||||
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
|
typ = self.type3
|
||||||
self.type3.get_substituted(smap[self.type3])
|
if isinstance(typ, types.PlaceholderForType) and typ in smap:
|
||||||
self.type3 = smap[self.type3]
|
typ = smap[typ]
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
if isinstance(typ, types.PlaceholderForType):
|
||||||
if isinstance(self.type3, types.PlaceholderForType):
|
|
||||||
return RequireTypeSubstitutes()
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
if 'BitWiseOr' == self.type_class3 and (self.type3 is types.u8 or self.type3 is types.u32 or self.type3 is types.u64):
|
if 'BitWiseOperation' == self.type_class3 and (typ is types.u8 or typ is types.u32 or typ is types.u64):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return Error(f'{self.type3.name} does not implement the {self.type_class3} type class')
|
if 'BasicMathOperation' == self.type_class3 and (
|
||||||
|
typ is types.u8 or typ is types.u32 or typ is types.u64
|
||||||
|
or typ is types.i32 or typ is types.i64
|
||||||
|
or typ is types.f32 or typ is types.f64
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return Error(f'{typ.name} does not implement the {self.type_class3} type class')
|
||||||
|
|
||||||
def human_readable(self) -> HumanReadableRet:
|
def human_readable(self) -> HumanReadableRet:
|
||||||
return (
|
return (
|
||||||
@ -169,7 +190,7 @@ class MustImplementTypeClassConstraint(ConstraintBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.type3)})'
|
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.type3)}, comment={repr(self.comment)})'
|
||||||
|
|
||||||
class LiteralFitsConstraint(ConstraintBase):
|
class LiteralFitsConstraint(ConstraintBase):
|
||||||
"""
|
"""
|
||||||
@ -180,11 +201,13 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
type3: types.Type3OrPlaceholder
|
type3: types.Type3OrPlaceholder
|
||||||
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]
|
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]
|
||||||
|
|
||||||
def __init__(self, type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]) -> None:
|
def __init__(self, type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple], comment: Optional[str] = None) -> None:
|
||||||
|
super().__init__(comment=comment)
|
||||||
|
|
||||||
self.type3 = type3
|
self.type3 = type3
|
||||||
self.literal = literal
|
self.literal = literal
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
def check(self, smap: SubstitutionMap) -> CheckResult:
|
||||||
int_table: Dict[str, Tuple[int, bool]] = {
|
int_table: Dict[str, Tuple[int, bool]] = {
|
||||||
'u8': (1, False),
|
'u8': (1, False),
|
||||||
'u32': (4, False),
|
'u32': (4, False),
|
||||||
@ -201,8 +224,11 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
|
|
||||||
def _check(type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]) -> CheckResult:
|
def _check(type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]) -> CheckResult:
|
||||||
if isinstance(type3, types.PlaceholderForType):
|
if isinstance(type3, types.PlaceholderForType):
|
||||||
|
if type3 not in smap:
|
||||||
return RequireTypeSubstitutes()
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
|
type3 = smap[type3]
|
||||||
|
|
||||||
val = literal.value
|
val = literal.value
|
||||||
|
|
||||||
if type3.name in int_table:
|
if type3.name in int_table:
|
||||||
@ -249,11 +275,6 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
|
|
||||||
return _check(self.type3, self.literal)
|
return _check(self.type3, self.literal)
|
||||||
|
|
||||||
def substitute_placeholders(self, smap: SubstitutionMap) -> None: # FIXME: Duplicate code
|
|
||||||
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
|
|
||||||
self.type3.get_substituted(smap[self.type3])
|
|
||||||
self.type3 = smap[self.type3]
|
|
||||||
|
|
||||||
def human_readable(self) -> HumanReadableRet:
|
def human_readable(self) -> HumanReadableRet:
|
||||||
return (
|
return (
|
||||||
'{literal} : {type3}',
|
'{literal} : {type3}',
|
||||||
@ -264,7 +285,7 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'LiteralFitsConstraint({repr(self.type3)}, {repr(self.literal)})'
|
return f'LiteralFitsConstraint({repr(self.type3)}, {repr(self.literal)}, comment={repr(self.comment)})'
|
||||||
|
|
||||||
class CanBeSubscriptedConstraint(ConstraintBase):
|
class CanBeSubscriptedConstraint(ConstraintBase):
|
||||||
"""
|
"""
|
||||||
@ -276,42 +297,45 @@ class CanBeSubscriptedConstraint(ConstraintBase):
|
|||||||
index: ourlang.Expression
|
index: ourlang.Expression
|
||||||
index_type3: types.Type3OrPlaceholder
|
index_type3: types.Type3OrPlaceholder
|
||||||
|
|
||||||
def __init__(self, type3: types.Type3OrPlaceholder, index: ourlang.Expression) -> None:
|
def __init__(self, type3: types.Type3OrPlaceholder, index: ourlang.Expression, comment: Optional[str] = None) -> None:
|
||||||
|
super().__init__(comment=comment)
|
||||||
|
|
||||||
self.type3 = type3
|
self.type3 = type3
|
||||||
self.index = index
|
self.index = index
|
||||||
self.index_type3 = index.type3
|
self.index_type3 = index.type3
|
||||||
|
|
||||||
def check(self) -> CheckResult:
|
def check(self, smap: SubstitutionMap) -> CheckResult:
|
||||||
if isinstance(self.type3, types.PlaceholderForType):
|
raise NotImplementedError
|
||||||
return RequireTypeSubstitutes()
|
# if isinstance(self.type3, types.PlaceholderForType):
|
||||||
|
# return RequireTypeSubstitutes()
|
||||||
if isinstance(self.index_type3, types.PlaceholderForType):
|
#
|
||||||
return RequireTypeSubstitutes()
|
# if isinstance(self.index_type3, types.PlaceholderForType):
|
||||||
|
# return RequireTypeSubstitutes()
|
||||||
if not isinstance(self.type3, types.AppliedType3):
|
#
|
||||||
return Error(f'Cannot subscript {self.type3:s}')
|
# if not isinstance(self.type3, types.AppliedType3):
|
||||||
|
# return Error(f'Cannot subscript {self.type3:s}')
|
||||||
if self.type3.base is types.tuple:
|
#
|
||||||
return None
|
# if self.type3.base is types.tuple:
|
||||||
|
# return None
|
||||||
raise NotImplementedError(self.type3)
|
#
|
||||||
|
# raise NotImplementedError(self.type3)
|
||||||
def get_new_placeholder_substitutes(self) -> SubstitutionMap:
|
#
|
||||||
if isinstance(self.type3, types.AppliedType3) and self.type3.base is types.tuple and isinstance(self.index_type3, types.PlaceholderForType):
|
# def get_new_placeholder_substitutes(self) -> SubstitutionMap:
|
||||||
return {
|
# if isinstance(self.type3, types.AppliedType3) and self.type3.base is types.tuple and isinstance(self.index_type3, types.PlaceholderForType):
|
||||||
self.index_type3: types.u32,
|
# return {
|
||||||
}
|
# self.index_type3: types.u32,
|
||||||
|
# }
|
||||||
return {}
|
#
|
||||||
|
# return {}
|
||||||
def substitute_placeholders(self, smap: SubstitutionMap) -> None: # FIXME: Duplicate code
|
#
|
||||||
if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
|
# def substitute_placeholders(self, smap: SubstitutionMap) -> None: # FIXME: Duplicate code
|
||||||
self.type3.get_substituted(smap[self.type3])
|
# if isinstance(self.type3, types.PlaceholderForType) and self.type3 in smap: # FIXME: Check recursive?
|
||||||
self.type3 = smap[self.type3]
|
# self.type3.get_substituted(smap[self.type3])
|
||||||
|
# self.type3 = smap[self.type3]
|
||||||
if isinstance(self.index_type3, types.PlaceholderForType) and self.index_type3 in smap: # FIXME: Check recursive?
|
#
|
||||||
self.index_type3.get_substituted(smap[self.index_type3])
|
# if isinstance(self.index_type3, types.PlaceholderForType) and self.index_type3 in smap: # FIXME: Check recursive?
|
||||||
self.index_type3 = smap[self.index_type3]
|
# self.index_type3.get_substituted(smap[self.index_type3])
|
||||||
|
# self.index_type3 = smap[self.index_type3]
|
||||||
|
|
||||||
def human_readable(self) -> HumanReadableRet:
|
def human_readable(self) -> HumanReadableRet:
|
||||||
return (
|
return (
|
||||||
@ -323,4 +347,4 @@ class CanBeSubscriptedConstraint(ConstraintBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'CanBeSubscriptedConstraint({repr(self.type3)}, {repr(self.index)})'
|
return f'CanBeSubscriptedConstraint({repr(self.type3)}, {repr(self.index)}, comment={repr(self.comment)})'
|
||||||
|
|||||||
@ -33,30 +33,41 @@ def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBas
|
|||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(inp, ourlang.VariableReference):
|
if isinstance(inp, ourlang.VariableReference):
|
||||||
yield SameTypeConstraint(inp.variable.type3, inp.type3, f'The type of a variable reference is the same as the type of variable {inp.variable.name}')
|
yield SameTypeConstraint(inp.variable.type3, inp.type3,
|
||||||
|
comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})')
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(inp, ourlang.BinaryOp):
|
if isinstance(inp, ourlang.BinaryOp):
|
||||||
if '|' == inp.operator:
|
if inp.operator in ('|', '&', '^', ):
|
||||||
yield from expression(ctx, inp.left)
|
yield from expression(ctx, inp.left)
|
||||||
yield from expression(ctx, inp.right)
|
yield from expression(ctx, inp.right)
|
||||||
|
|
||||||
yield MustImplementTypeClassConstraint('BitWiseOr', inp.left.type3)
|
yield MustImplementTypeClassConstraint('BitWiseOperation', inp.left.type3)
|
||||||
yield SameTypeConstraint(inp.right.type3, inp.left.type3, '(|) :: a -> a -> a')
|
yield SameTypeConstraint(inp.left.type3, inp.right.type3, inp.type3,
|
||||||
yield SameTypeConstraint(inp.type3, inp.right.type3, '(|) :: a -> a -> a')
|
comment=f'({inp.operator}) :: a -> a -> a')
|
||||||
|
return
|
||||||
|
|
||||||
|
if inp.operator in ('+', '-', '*', '/', ):
|
||||||
|
yield from expression(ctx, inp.left)
|
||||||
|
yield from expression(ctx, inp.right)
|
||||||
|
|
||||||
|
yield MustImplementTypeClassConstraint('BasicMathOperation', inp.left.type3)
|
||||||
|
yield SameTypeConstraint(inp.left.type3, inp.right.type3, inp.type3,
|
||||||
|
comment=f'({inp.operator}) :: a -> a -> a')
|
||||||
return
|
return
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp)
|
raise NotImplementedError(expression, inp)
|
||||||
|
|
||||||
if isinstance(inp, ourlang.FunctionCall):
|
if isinstance(inp, ourlang.FunctionCall):
|
||||||
yield SameTypeConstraint(inp.function.returns_type3, inp.type3, f'The type of a function call to {inp.function.name} is the same as the type that the function returns')
|
yield SameTypeConstraint(inp.function.returns_type3, inp.type3,
|
||||||
|
comment=f'The type of a function call to {inp.function.name} is the same as the type that the function returns')
|
||||||
|
|
||||||
assert len(inp.arguments) == len(inp.function.posonlyargs) # FIXME: Make this a Constraint
|
assert len(inp.arguments) == len(inp.function.posonlyargs) # FIXME: Make this a Constraint
|
||||||
|
|
||||||
for fun_arg, call_arg in zip(inp.function.posonlyargs, inp.arguments):
|
for fun_arg, call_arg in zip(inp.function.posonlyargs, inp.arguments):
|
||||||
yield from expression(ctx, call_arg)
|
yield from expression(ctx, call_arg)
|
||||||
yield SameTypeConstraint(fun_arg.type3, call_arg.type3,
|
yield SameTypeConstraint(fun_arg.type3, call_arg.type3,
|
||||||
f'The type of the value passed to argument {fun_arg.name} of function {inp.function.name} should match the type of that argument')
|
comment=f'The type of the value passed to argument {fun_arg.name} of function {inp.function.name} should match the type of that argument')
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -69,7 +80,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBas
|
|||||||
|
|
||||||
if isinstance(inp, ourlang.AccessStructMember):
|
if isinstance(inp, ourlang.AccessStructMember):
|
||||||
yield SameTypeConstraint(inp.struct_type3.members[inp.member], inp.type3,
|
yield SameTypeConstraint(inp.struct_type3.members[inp.member], inp.type3,
|
||||||
f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
|
comment=f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
|
||||||
return
|
return
|
||||||
|
|
||||||
raise NotImplementedError(expression, inp)
|
raise NotImplementedError(expression, inp)
|
||||||
@ -83,11 +94,13 @@ def function(ctx: Context, inp: ourlang.Function) -> Generator[ConstraintBase, N
|
|||||||
|
|
||||||
yield from expression(ctx, inp.statements[0].value)
|
yield from expression(ctx, inp.statements[0].value)
|
||||||
|
|
||||||
yield SameTypeConstraint(inp.returns_type3, inp.statements[0].value.type3, f'The type of the value returned from function {inp.name} should match its return type')
|
yield SameTypeConstraint(inp.returns_type3, inp.statements[0].value.type3,
|
||||||
|
comment=f'The type of the value returned from function {inp.name} should match its return type')
|
||||||
|
|
||||||
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> Generator[ConstraintBase, None, None]:
|
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> Generator[ConstraintBase, None, None]:
|
||||||
yield from constant(ctx, inp.constant)
|
yield from constant(ctx, inp.constant)
|
||||||
yield SameTypeConstraint(inp.type3, inp.constant.type3, f'The type of the value for module constant definition {inp.name} should match the type of that constant')
|
yield SameTypeConstraint(inp.type3, inp.constant.type3,
|
||||||
|
comment=f'The type of the value for module constant definition {inp.name} should match the type of that constant')
|
||||||
|
|
||||||
def module(ctx: Context, inp: ourlang.Module) -> Generator[ConstraintBase, None, None]:
|
def module(ctx: Context, inp: ourlang.Module) -> Generator[ConstraintBase, None, None]:
|
||||||
for cdef in inp.constant_defs.values():
|
for cdef in inp.constant_defs.values():
|
||||||
|
|||||||
@ -24,48 +24,74 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
|
|||||||
placeholder_substitutes: Dict[PlaceholderForType, Type3] = {}
|
placeholder_substitutes: Dict[PlaceholderForType, Type3] = {}
|
||||||
placeholder_id_map: Dict[int, str] = {}
|
placeholder_id_map: Dict[int, str] = {}
|
||||||
|
|
||||||
restack_counter = 0
|
|
||||||
|
|
||||||
error_list: List[Error] = []
|
error_list: List[Error] = []
|
||||||
while constraint_list:
|
for _ in range(MAX_RESTACK_COUNT):
|
||||||
if verbose:
|
if verbose:
|
||||||
print()
|
print()
|
||||||
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
|
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
|
||||||
|
|
||||||
constraint = constraint_list.pop(0)
|
old_constraint_ids = {id(x) for x in constraint_list}
|
||||||
|
old_placeholder_substitutes_len = len(placeholder_substitutes)
|
||||||
|
|
||||||
constraint.substitute_placeholders(placeholder_substitutes)
|
new_constraint_list = []
|
||||||
placeholder_substitutes.update(constraint.get_new_placeholder_substitutes())
|
for constraint in constraint_list:
|
||||||
|
check_result = constraint.check(placeholder_substitutes)
|
||||||
check_result = constraint.check()
|
|
||||||
if check_result is None:
|
if check_result is None:
|
||||||
if verbose:
|
if verbose:
|
||||||
print('Constraint checks out')
|
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
|
continue
|
||||||
|
|
||||||
if isinstance(check_result, Error):
|
if isinstance(check_result, Error):
|
||||||
error_list.append(check_result)
|
error_list.append(check_result)
|
||||||
if verbose:
|
if verbose:
|
||||||
print('Got an error')
|
print_constraint(placeholder_id_map, constraint)
|
||||||
|
print('-> Got an error')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(check_result, RequireTypeSubstitutes):
|
if isinstance(check_result, RequireTypeSubstitutes):
|
||||||
# FIXME: How to detect infinite loop? Is that necessary?
|
new_constraint_list.append(constraint)
|
||||||
restack_counter += 1
|
|
||||||
if restack_counter > MAX_RESTACK_COUNT:
|
|
||||||
raise Exception('This looks like an infinite loop', constraint_list)
|
|
||||||
|
|
||||||
constraint_list.append(constraint)
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print('Back on the todo list')
|
print_constraint(placeholder_id_map, constraint)
|
||||||
|
print('-> Back on the todo list')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
raise NotImplementedError(constraint, check_result)
|
raise NotImplementedError(constraint, check_result)
|
||||||
|
|
||||||
|
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:
|
||||||
|
raise Exception('Cannot type this program - not enough information')
|
||||||
|
|
||||||
|
constraint_list = new_constraint_list
|
||||||
|
|
||||||
|
if constraint_list:
|
||||||
|
raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations')
|
||||||
|
|
||||||
if error_list:
|
if error_list:
|
||||||
raise Type3Exception(error_list)
|
raise Type3Exception(error_list)
|
||||||
|
|
||||||
# TODO: Implement type substitution on the AST
|
# 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:
|
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None:
|
||||||
txt, fmt = constraint.human_readable()
|
txt, fmt = constraint.human_readable()
|
||||||
@ -88,15 +114,15 @@ def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintB
|
|||||||
|
|
||||||
act_fmt[fmt_key] = fmt_val
|
act_fmt[fmt_key] = fmt_val
|
||||||
|
|
||||||
if 'comment' in act_fmt:
|
if constraint.comment is not None:
|
||||||
print('- ' + txt.format(**act_fmt).ljust(40) + '; ' + act_fmt['comment'])
|
print('- ' + txt.format(**act_fmt).ljust(40) + '; ' + constraint.comment)
|
||||||
else:
|
else:
|
||||||
print('- ' + txt.format(**act_fmt))
|
print('- ' + txt.format(**act_fmt))
|
||||||
|
|
||||||
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
|
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
|
||||||
print('=== v type3 constraint_list v === ')
|
print('=== v type3 constraint_list v === ')
|
||||||
for psk, psv in placeholder_substitutes.items():
|
for psk, psv in placeholder_substitutes.items():
|
||||||
print_constraint(placeholder_id_map, SameTypeConstraint(psk, psv, 'Deduced type'))
|
print_constraint(placeholder_id_map, SameTypeConstraint(psk, psv, comment='Deduced type'))
|
||||||
|
|
||||||
for constraint in constraint_list:
|
for constraint in constraint_list:
|
||||||
print_constraint(placeholder_id_map, constraint)
|
print_constraint(placeholder_id_map, constraint)
|
||||||
|
|||||||
@ -67,21 +67,6 @@ class PlaceholderForType:
|
|||||||
def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None:
|
def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None:
|
||||||
self.update_on_substitution = [*update_on_substitution]
|
self.update_on_substitution = [*update_on_substitution]
|
||||||
|
|
||||||
def get_substituted(self, result_type: Type3) -> None:
|
|
||||||
"""
|
|
||||||
Informs this Placeholder that it's getting substituted
|
|
||||||
|
|
||||||
This will also clear the update_on_substitution list
|
|
||||||
"""
|
|
||||||
for uos in self.update_on_substitution:
|
|
||||||
uos.type3 = result_type
|
|
||||||
|
|
||||||
self.update_on_substitution = []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
return f'T{id(self)}'
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
uos = ', '.join(repr(x) for x in self.update_on_substitution)
|
uos = ', '.join(repr(x) for x in self.update_on_substitution)
|
||||||
|
|
||||||
|
|||||||
@ -151,7 +151,7 @@ def testEntry() -> f64:
|
|||||||
return 10.0 | 3.0
|
return 10.0 | 3.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with pytest.raises(Type3Exception, match='f64 does not implement the BitWiseOr type class'):
|
with pytest.raises(Type3Exception, match='f64 does not implement the BitWiseOperation type class'):
|
||||||
Suite(code_py).run_code()
|
Suite(code_py).run_code()
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@ -297,17 +297,33 @@ def testEntry() -> {type_}:
|
|||||||
assert TYPE_MAP[type_] == type(result.returned_value)
|
assert TYPE_MAP[type_] == type(result.returned_value)
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', COMPLETE_NUMERIC_TYPES)
|
@pytest.mark.parametrize('type_', COMPLETE_INT_TYPES)
|
||||||
def test_division_zero_let_it_crash(type_):
|
def test_division_zero_let_it_crash_int(type_):
|
||||||
code_py = f"""
|
code_py = f"""
|
||||||
@exported
|
@exported
|
||||||
def testEntry() -> {type_}:
|
def testEntry() -> {type_}:
|
||||||
return 10 / 0
|
return 10 / 0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# WebAssembly dictates that integer division is a partial operator (e.g. unreachable for 0)
|
||||||
|
# https://www.w3.org/TR/wasm-core-1/#-hrefop-idiv-umathrmidiv_u_n-i_1-i_2
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
Suite(code_py).run_code()
|
Suite(code_py).run_code()
|
||||||
|
|
||||||
|
@pytest.mark.integration_test
|
||||||
|
@pytest.mark.parametrize('type_', ALL_FLOAT_TYPES)
|
||||||
|
def test_division_zero_let_it_crash_float(type_):
|
||||||
|
code_py = f"""
|
||||||
|
@exported
|
||||||
|
def testEntry() -> {type_}:
|
||||||
|
return 10.0 / 0.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
# WebAssembly dictates that float division follows the IEEE rules
|
||||||
|
# https://www.w3.org/TR/wasm-core-1/#-hrefop-fdivmathrmfdiv_n-z_1-z_2
|
||||||
|
result = Suite(code_py).run_code()
|
||||||
|
assert float('+inf') == result.returned_value
|
||||||
|
|
||||||
@pytest.mark.integration_test
|
@pytest.mark.integration_test
|
||||||
@pytest.mark.parametrize('type_', ['f32', 'f64'])
|
@pytest.mark.parametrize('type_', ['f32', 'f64'])
|
||||||
def test_builtins_sqrt(type_):
|
def test_builtins_sqrt(type_):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user