Re-implementing some things that were broken. Also, if a typing error is found, and we detect an infinite loop, we return the errors instead, as that's probably what causing the loop anyhow.
355 lines
11 KiB
Python
355 lines
11 KiB
Python
"""
|
|
This module contains possible constraints generated based on the AST
|
|
|
|
These need to be resolved before the program can be compiled.
|
|
"""
|
|
from typing import Dict, Optional, List, Tuple, Union
|
|
|
|
from .. import ourlang
|
|
|
|
from . import types
|
|
|
|
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:
|
|
self.msg = msg
|
|
|
|
def __repr__(self) -> str:
|
|
return f'Error({repr(self.msg)})'
|
|
|
|
class RequireTypeSubstitutes:
|
|
"""
|
|
Returned by the check function for a contraint if they do not have all
|
|
their types substituted yet.
|
|
|
|
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]
|
|
|
|
CheckResult = Union[None, SubstitutionMap, Error, RequireTypeSubstitutes]
|
|
|
|
HumanReadableRet = Tuple[str, Dict[str, Union[str, ourlang.Expression, types.Type3, types.PlaceholderForType]]]
|
|
|
|
class Context:
|
|
"""
|
|
Context for constraints
|
|
"""
|
|
|
|
__slots__ = ()
|
|
|
|
class ConstraintBase:
|
|
"""
|
|
Base class for constraints
|
|
"""
|
|
__slots__ = ('comment', )
|
|
|
|
comment: Optional[str]
|
|
"""
|
|
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
|
|
|
|
def human_readable(self) -> HumanReadableRet:
|
|
"""
|
|
Returns a more human readable form of this constraint
|
|
"""
|
|
return repr(self), {}
|
|
|
|
class SameTypeConstraint(ConstraintBase):
|
|
"""
|
|
Verifies that a number of types all are the same type
|
|
"""
|
|
__slots__ = ('type_list', )
|
|
|
|
type_list: List[types.Type3OrPlaceholder]
|
|
|
|
def __init__(self, *type_list: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
|
|
super().__init__(comment=comment)
|
|
|
|
assert len(type_list) > 1
|
|
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()
|
|
|
|
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 {
|
|
typ: first_type
|
|
for typ in placeholders
|
|
}
|
|
|
|
def human_readable(self) -> HumanReadableRet:
|
|
return (
|
|
' == '.join('{t' + str(idx) + '}' for idx in range(len(self.type_list))),
|
|
{
|
|
't' + str(idx): typ
|
|
for idx, typ in enumerate(self.type_list)
|
|
},
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
args = ', '.join(repr(x) for x in self.type_list)
|
|
|
|
return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
|
|
|
|
class MustImplementTypeClassConstraint(ConstraintBase):
|
|
"""
|
|
A type must implement a given type class
|
|
"""
|
|
__slots__ = ('type_class3', 'type3', )
|
|
|
|
type_class3: str
|
|
type3: types.Type3OrPlaceholder
|
|
|
|
DATA = {
|
|
'u8': {'BitWiseOperation', 'BasicMathOperation'},
|
|
'u32': {'BitWiseOperation', 'BasicMathOperation'},
|
|
'u64': {'BitWiseOperation', 'BasicMathOperation'},
|
|
'i32': {'BasicMathOperation'},
|
|
'i64': {'BasicMathOperation'},
|
|
'bytes': {'Sized'},
|
|
'f32': {'BasicMathOperation', 'FloatingPoint'},
|
|
'f64': {'BasicMathOperation', 'FloatingPoint'},
|
|
}
|
|
|
|
def __init__(self, type_class3: str, type3: types.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
|
|
super().__init__(comment=comment)
|
|
|
|
self.type_class3 = type_class3
|
|
self.type3 = type3
|
|
|
|
def check(self, smap: SubstitutionMap) -> CheckResult:
|
|
typ = self.type3
|
|
if isinstance(typ, types.PlaceholderForType) and typ in smap:
|
|
typ = smap[typ]
|
|
|
|
if isinstance(typ, types.PlaceholderForType):
|
|
return RequireTypeSubstitutes()
|
|
|
|
if self.type_class3 in self.__class__.DATA.get(typ.name, set()):
|
|
return None
|
|
|
|
return Error(f'{typ.name} does not implement the {self.type_class3} type class')
|
|
|
|
def human_readable(self) -> HumanReadableRet:
|
|
return (
|
|
'{type3} derives {type_class3}',
|
|
{
|
|
'type_class3': self.type_class3,
|
|
'type3': self.type3,
|
|
},
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.type3)}, comment={repr(self.comment)})'
|
|
|
|
class LiteralFitsConstraint(ConstraintBase):
|
|
"""
|
|
A literal value fits a given type
|
|
"""
|
|
__slots__ = ('type3', 'literal', )
|
|
|
|
type3: types.Type3OrPlaceholder
|
|
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]
|
|
|
|
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.literal = literal
|
|
|
|
def check(self, smap: SubstitutionMap) -> CheckResult:
|
|
int_table: Dict[str, Tuple[int, bool]] = {
|
|
'u8': (1, False),
|
|
'u32': (4, False),
|
|
'u64': (8, False),
|
|
'i8': (1, True),
|
|
'i32': (4, True),
|
|
'i64': (8, True),
|
|
}
|
|
|
|
float_table: Dict[str, None] = {
|
|
'f32': None,
|
|
'f64': None,
|
|
}
|
|
|
|
def _check(type3: types.Type3OrPlaceholder, literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantTuple]) -> CheckResult:
|
|
if isinstance(type3, types.PlaceholderForType):
|
|
if type3 not in smap:
|
|
return RequireTypeSubstitutes()
|
|
|
|
type3 = smap[type3]
|
|
|
|
val = literal.value
|
|
|
|
if type3.name in int_table:
|
|
bts, sgn = int_table[type3.name]
|
|
|
|
if isinstance(val, int):
|
|
try:
|
|
val.to_bytes(bts, 'big', signed=sgn)
|
|
except OverflowError:
|
|
return Error(f'Must fit in {bts} byte(s)') # FIXME: Add line information
|
|
|
|
return None
|
|
|
|
return Error('Must be integer') # FIXME: Add line information
|
|
|
|
|
|
if type3.name in float_table:
|
|
_ = float_table[type3.name]
|
|
|
|
if isinstance(val, float):
|
|
# FIXME: Bit check
|
|
|
|
return None
|
|
|
|
return Error('Must be real') # FIXME: Add line information
|
|
|
|
if isinstance(type3, types.AppliedType3) and type3.base is types.tuple:
|
|
if not isinstance(literal, ourlang.ConstantTuple):
|
|
return Error('Must be tuple')
|
|
|
|
assert isinstance(val, list) # type hint
|
|
|
|
if len(type3.args) != len(val):
|
|
return Error('Tuple element count mismatch')
|
|
|
|
for elt_typ, elt_lit in zip(type3.args, val):
|
|
res = _check(elt_typ, elt_lit)
|
|
if res is not None:
|
|
return res
|
|
|
|
return None
|
|
|
|
raise NotImplementedError
|
|
|
|
return _check(self.type3, self.literal)
|
|
|
|
def human_readable(self) -> HumanReadableRet:
|
|
return (
|
|
'{literal} : {type3}',
|
|
{
|
|
'literal': self.literal,
|
|
'type3': self.type3,
|
|
},
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f'LiteralFitsConstraint({repr(self.type3)}, {repr(self.literal)}, comment={repr(self.comment)})'
|
|
|
|
class CanBeSubscriptedConstraint(ConstraintBase):
|
|
"""
|
|
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
|
|
"""
|
|
__slots__ = ('type3', 'index', 'index_type3', )
|
|
|
|
type3: types.Type3OrPlaceholder
|
|
index: ourlang.Expression
|
|
index_type3: types.Type3OrPlaceholder
|
|
|
|
def __init__(self, type3: types.Type3OrPlaceholder, index: ourlang.Expression, comment: Optional[str] = None) -> None:
|
|
super().__init__(comment=comment)
|
|
|
|
self.type3 = type3
|
|
self.index = index
|
|
self.index_type3 = index.type3
|
|
|
|
def check(self, smap: SubstitutionMap) -> CheckResult:
|
|
raise NotImplementedError
|
|
# if isinstance(self.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 self.type3.base is types.tuple:
|
|
# return None
|
|
#
|
|
# 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):
|
|
# return {
|
|
# self.index_type3: types.u32,
|
|
# }
|
|
#
|
|
# 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?
|
|
# 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])
|
|
# self.index_type3 = smap[self.index_type3]
|
|
|
|
def human_readable(self) -> HumanReadableRet:
|
|
return (
|
|
'{type3}[{index}]',
|
|
{
|
|
'type3': self.type3,
|
|
'index': self.index,
|
|
},
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f'CanBeSubscriptedConstraint({repr(self.type3)}, {repr(self.index)}, comment={repr(self.comment)})'
|