Compare commits

...

3 Commits

Author SHA1 Message Date
Johan B.W. de Vries
a2774d0fa6 Removes the special casing for foldl
Has to implement both functions as arguments and type
place holders (variables) for type constructors.
2025-04-27 15:32:16 +02:00
Johan B.W. de Vries
11fde4cb9e Add missing fix for generator 2025-04-27 15:30:17 +02:00
Johan B.W. de Vries
c8009403c4 Separates out TypeVariable and constraints
They look a lot like placeholders, but they exist before
the typing system takes place. And there's a (much smaller)
context to deal with.

For now, removes Placeholders in user function definitions
as they were not implemented.

Adds signature to function to try to get them closer to
type class methods. Already seeing some benefit in the
constraint generator.

Stricter zipping for safety.
2025-04-27 15:28:08 +02:00
13 changed files with 321 additions and 273 deletions

View File

@ -116,10 +116,6 @@ def expression(inp: ourlang.Expression) -> str:
if isinstance(inp, ourlang.AccessStructMember):
return f'{expression(inp.varref)}.{inp.member}'
if isinstance(inp, ourlang.Fold):
fold_name = 'foldl' if ourlang.Fold.Direction.LEFT == inp.dir else 'foldr'
return f'{fold_name}({inp.func.name}, {expression(inp.base)}, {expression(inp.iter)})'
raise NotImplementedError(expression, inp)
def statement(inp: ourlang.Statement) -> Statements:

View File

@ -4,10 +4,11 @@ This module contains the code to convert parsed Ourlang into WebAssembly code
import struct
from typing import Dict, List, Optional
from . import codestyle, ourlang, prelude, wasm
from . import ourlang, prelude, wasm
from .runtime import calculate_alloc_size, calculate_member_offset
from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types
from .type3 import functions as type3functions
from .type3 import placeholders as type3placeholders
from .type3 import typeclasses as type3classes
from .type3 import types as type3types
@ -335,7 +336,7 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
# Store each element individually
offset = 0
for element, exp_type3 in zip(inp.elements, args):
for element, exp_type3 in zip(inp.elements, args, strict=True):
if isinstance(exp_type3, type3placeholders.PlaceholderForType):
assert exp_type3.resolve_as is not None
assert isinstance(exp_type3.resolve_as, type3types.Type3)
@ -434,10 +435,10 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
type_var_map: Dict[type3classes.TypeVariable, type3types.Type3] = {}
type_var_map: Dict[type3functions.TypeVariable, type3types.Type3] = {}
for type_var, arg_expr in zip(inp.operator.signature, [inp.left, inp.right, inp]):
if not isinstance(type_var, type3classes.TypeVariable):
for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True):
if not isinstance(type_var, type3functions.TypeVariable):
# Fixed type, not part of the lookup requirements
continue
@ -476,8 +477,8 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
# FIXME: Duplicate code with BinaryOp
type_var_map = {}
for type_var, arg_expr in zip(inp.function.signature, inp.arguments + [inp]):
if not isinstance(type_var, type3classes.TypeVariable):
for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True):
if not isinstance(type_var, type3functions.TypeVariable):
# Fixed type, not part of the lookup requirements
continue
@ -587,89 +588,85 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
)))
return
if isinstance(inp, ourlang.Fold):
expression_fold(wgn, inp)
return
raise NotImplementedError(expression, inp)
def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
"""
Compile: Fold expression
"""
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
# def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
# """
# Compile: Fold expression
# """
# assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR
if inp.iter.type3 is not prelude.bytes_:
raise NotImplementedError(expression_fold, inp, inp.iter.type3)
# if inp.iter.type3 is not prelude.bytes_:
# raise NotImplementedError(expression_fold, inp, inp.iter.type3)
wgn.add_statement('nop', comment='acu :: u8')
acu_var = wgn.temp_var_u8(f'fold_{codestyle.type3(inp.type3)}_acu')
wgn.add_statement('nop', comment='adr :: bytes*')
adr_var = wgn.temp_var_i32('fold_i32_adr')
wgn.add_statement('nop', comment='len :: i32')
len_var = wgn.temp_var_i32('fold_i32_len')
# wgn.add_statement('nop', comment='acu :: u8')
# acu_var = wgn.temp_var_u8(f'fold_{codestyle.type3(inp.type3)}_acu')
# wgn.add_statement('nop', comment='adr :: bytes*')
# adr_var = wgn.temp_var_i32('fold_i32_adr')
# wgn.add_statement('nop', comment='len :: i32')
# len_var = wgn.temp_var_i32('fold_i32_len')
wgn.add_statement('nop', comment='acu = base')
expression(wgn, inp.base)
wgn.local.set(acu_var)
# wgn.add_statement('nop', comment='acu = base')
# expression(wgn, inp.base)
# wgn.local.set(acu_var)
wgn.add_statement('nop', comment='adr = adr(iter)')
expression(wgn, inp.iter)
wgn.local.set(adr_var)
# wgn.add_statement('nop', comment='adr = adr(iter)')
# expression(wgn, inp.iter)
# wgn.local.set(adr_var)
wgn.add_statement('nop', comment='len = len(iter)')
wgn.local.get(adr_var)
wgn.i32.load()
wgn.local.set(len_var)
# wgn.add_statement('nop', comment='len = len(iter)')
# wgn.local.get(adr_var)
# wgn.i32.load()
# wgn.local.set(len_var)
wgn.add_statement('nop', comment='i = 0')
idx_var = wgn.temp_var_i32(f'fold_{codestyle.type3(inp.type3)}_idx')
wgn.i32.const(0)
wgn.local.set(idx_var)
# wgn.add_statement('nop', comment='i = 0')
# idx_var = wgn.temp_var_i32(f'fold_{codestyle.type3(inp.type3)}_idx')
# wgn.i32.const(0)
# wgn.local.set(idx_var)
wgn.add_statement('nop', comment='if i < len')
wgn.local.get(idx_var)
wgn.local.get(len_var)
wgn.i32.lt_u()
with wgn.if_():
# From here on, adr_var is the address of byte we're referencing
# This is akin to calling stdlib_types.__subscript_bytes__
# But since we already know we are inside of bounds,
# can just bypass it and load the memory directly.
wgn.local.get(adr_var)
wgn.i32.const(3) # Bytes header -1, since we do a +1 every loop
wgn.i32.add()
wgn.local.set(adr_var)
# wgn.add_statement('nop', comment='if i < len')
# wgn.local.get(idx_var)
# wgn.local.get(len_var)
# wgn.i32.lt_u()
# with wgn.if_():
# # From here on, adr_var is the address of byte we're referencing
# # This is akin to calling stdlib_types.__subscript_bytes__
# # But since we already know we are inside of bounds,
# # can just bypass it and load the memory directly.
# wgn.local.get(adr_var)
# wgn.i32.const(3) # Bytes header -1, since we do a +1 every loop
# wgn.i32.add()
# wgn.local.set(adr_var)
wgn.add_statement('nop', comment='while True')
with wgn.loop():
wgn.add_statement('nop', comment='acu = func(acu, iter[i])')
wgn.local.get(acu_var)
# wgn.add_statement('nop', comment='while True')
# with wgn.loop():
# wgn.add_statement('nop', comment='acu = func(acu, iter[i])')
# wgn.local.get(acu_var)
# Get the next byte, write back the address
wgn.local.get(adr_var)
wgn.i32.const(1)
wgn.i32.add()
wgn.local.tee(adr_var)
wgn.i32.load8_u()
# # Get the next byte, write back the address
# wgn.local.get(adr_var)
# wgn.i32.const(1)
# wgn.i32.add()
# wgn.local.tee(adr_var)
# wgn.i32.load8_u()
wgn.add_statement('call', f'${inp.func.name}')
wgn.local.set(acu_var)
# wgn.add_statement('call', f'${inp.func.name}')
# wgn.local.set(acu_var)
wgn.add_statement('nop', comment='i = i + 1')
wgn.local.get(idx_var)
wgn.i32.const(1)
wgn.i32.add()
wgn.local.set(idx_var)
# wgn.add_statement('nop', comment='i = i + 1')
# wgn.local.get(idx_var)
# wgn.i32.const(1)
# wgn.i32.add()
# wgn.local.set(idx_var)
wgn.add_statement('nop', comment='if i >= len: break')
wgn.local.get(idx_var)
wgn.local.get(len_var)
wgn.i32.lt_u()
wgn.br_if(0)
# wgn.add_statement('nop', comment='if i >= len: break')
# wgn.local.get(idx_var)
# wgn.local.get(len_var)
# wgn.i32.lt_u()
# wgn.br_if(0)
# return acu
wgn.local.get(acu_var)
# # return acu
# wgn.local.get(acu_var)
def statement_return(wgn: WasmGenerator, inp: ourlang.StatementReturn) -> None:
"""

View File

@ -1,10 +1,10 @@
"""
Contains the syntax tree for ourlang
"""
import enum
from typing import Dict, Iterable, List, Optional, Union
from . import prelude
from .type3.functions import FunctionSignature, TypeVariableContext
from .type3.placeholders import PlaceholderForType, Type3OrPlaceholder
from .type3.typeclasses import Type3ClassMethod
from .type3.types import Type3
@ -222,36 +222,6 @@ class AccessStructMember(Expression):
self.struct_type3 = struct_type3
self.member = member
class Fold(Expression):
"""
A (left or right) fold
"""
class Direction(enum.Enum):
"""
Which direction to fold in
"""
LEFT = 0
RIGHT = 1
dir: Direction
func: 'Function'
base: Expression
iter: Expression
def __init__(
self,
dir_: Direction,
func: 'Function',
base: Expression,
iter_: Expression,
) -> None:
super().__init__()
self.dir = dir_
self.func = func
self.base = base
self.iter = iter_
class Statement:
"""
A statement within a function
@ -300,21 +270,22 @@ class FunctionParam:
name: str
type3: Type3OrPlaceholder
def __init__(self, name: str, type3: Optional[Type3]) -> None:
def __init__(self, name: str, type3: Type3) -> None:
self.name = name
self.type3 = PlaceholderForType([self]) if type3 is None else type3
self.type3 = type3
class Function:
"""
A function processes input and produces output
"""
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_type3', 'posonlyargs', )
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'signature', 'returns_type3', 'posonlyargs', )
name: str
lineno: int
exported: bool
imported: Optional[str]
statements: List[Statement]
signature: FunctionSignature
returns_type3: Type3
posonlyargs: List[FunctionParam]
@ -324,6 +295,7 @@ class Function:
self.exported = False
self.imported = None
self.statements = []
self.signature = FunctionSignature(TypeVariableContext(), [])
self.returns_type3 = prelude.none # FIXME: This could be a placeholder
self.posonlyargs = []
@ -354,12 +326,14 @@ class StructConstructor(Function):
def __init__(self, struct_type3: Type3) -> None:
super().__init__(f'@{struct_type3.name}@__init___@', -1)
self.returns_type3 = struct_type3
st_args = prelude.struct.did_construct(struct_type3)
assert st_args is not None
for mem, typ in st_args.items():
self.posonlyargs.append(FunctionParam(mem, typ, ))
self.signature.args.append(typ)
self.returns_type3 = struct_type3
self.signature.args.append(struct_type3)
self.struct_type3 = struct_type3

View File

@ -14,7 +14,6 @@ from .ourlang import (
ConstantStruct,
ConstantTuple,
Expression,
Fold,
Function,
FunctionCall,
FunctionParam,
@ -159,9 +158,18 @@ class OurVisitor:
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
for arg in node.args.args:
if arg.annotation is None:
_raise_static_error(node, 'Must give a argument type')
arg_type = self.visit_type(module, arg.annotation)
# if isisntance(arg_type, TypeVariable):
# arg_type = PlaceHolderForType()
function.signature.args.append(arg_type)
function.posonlyargs.append(FunctionParam(
arg.arg,
self.visit_type(module, arg.annotation) if arg.annotation else None,
arg_type,
))
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
@ -202,11 +210,11 @@ class OurVisitor:
else:
function.imported = 'imports'
if node.returns is not None: # Note: `-> None` would be a ast.Constant
function.returns_type3 = self.visit_type(module, node.returns)
else:
# FIXME: Mostly works already, needs to fix Function.returns_type3 and have it updated
raise NotImplementedError('Function without an explicit return type')
if node.returns is None: # Note: `-> None` would be a ast.Constant
_raise_static_error(node, 'Must give a return type')
return_type = self.visit_type(module, node.returns)
function.signature.args.append(return_type)
function.returns_type3 = return_type
_not_implemented(not node.type_comment, 'FunctionDef.type_comment')
@ -467,7 +475,7 @@ class OurVisitor:
raise NotImplementedError(f'{node} as expr in FunctionDef')
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[Fold, FunctionCall, UnaryOp]:
def visit_Module_FunctionDef_Call(self, module: Module, function: Function, our_locals: OurLocals, node: ast.Call) -> Union[FunctionCall, UnaryOp]:
if node.keywords:
_raise_static_error(node, 'Keyword calling not supported') # Yet?
@ -490,35 +498,35 @@ class OurVisitor:
)
unary_op.type3 = prelude.u32
return unary_op
elif node.func.id == 'foldl':
if 3 != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires 3 arguments but {len(node.args)} are given')
# elif node.func.id == 'foldl':
# if 3 != len(node.args):
# _raise_static_error(node, f'Function {node.func.id} requires 3 arguments but {len(node.args)} are given')
# TODO: This is not generic, you cannot return a function
subnode = node.args[0]
if not isinstance(subnode, ast.Name):
raise NotImplementedError(f'Calling methods that are not a name {subnode}')
if not isinstance(subnode.ctx, ast.Load):
_raise_static_error(subnode, 'Must be load context')
if subnode.id not in module.functions:
_raise_static_error(subnode, 'Reference to undefined function')
func = module.functions[subnode.id]
if 2 != len(func.posonlyargs):
_raise_static_error(node, f'Function {node.func.id} requires a function with 2 arguments but a function with {len(func.posonlyargs)} args is given')
# # TODO: This is not generic, you cannot return a function
# subnode = node.args[0]
# if not isinstance(subnode, ast.Name):
# raise NotImplementedError(f'Calling methods that are not a name {subnode}')
# if not isinstance(subnode.ctx, ast.Load):
# _raise_static_error(subnode, 'Must be load context')
# if subnode.id not in module.functions:
# _raise_static_error(subnode, 'Reference to undefined function')
# func = module.functions[subnode.id]
# if 2 != len(func.posonlyargs):
# _raise_static_error(node, f'Function {node.func.id} requires a function with 2 arguments but a function with {len(func.posonlyargs)} args is given')
return Fold(
Fold.Direction.LEFT,
func,
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[1]),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[2]),
)
# return Fold(
# Fold.Direction.LEFT,
# func,
# self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[1]),
# self.visit_Module_FunctionDef_expr(module, function, our_locals, node.args[2]),
# )
else:
if node.func.id not in module.functions:
_raise_static_error(node, 'Call to undefined function')
func = module.functions[node.func.id]
exp_arg_count = len(func.posonlyargs) if isinstance(func, Function) else len(func.signature) - 1
exp_arg_count = len(func.signature.args) - 1
if exp_arg_count != len(node.args):
_raise_static_error(node, f'Function {node.func.id} requires {exp_arg_count} arguments but {len(node.args)} are given')

View File

@ -2,7 +2,12 @@
The prelude are all the builtin types, type classes and methods
"""
from ..type3.typeclasses import Type3Class, TypeVariable, instance_type_class
from ..type3.typeclasses import (
Type3Class,
TypeConstructorVariable,
TypeVariable,
instance_type_class,
)
from ..type3.types import (
Type3,
TypeConstructor_StaticArray,
@ -121,7 +126,8 @@ PRELUDE_TYPES: dict[str, Type3] = {
}
a = TypeVariable('a')
b = TypeVariable('b')
t = TypeConstructorVariable('t')
InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={})
"""
@ -247,6 +253,10 @@ Sized_ = Type3Class('Sized', [a], methods={
instance_type_class(Sized_, bytes_)
Foldable = Type3Class('Foldable', [t], methods={
'foldl': [[a, b, b], b, t(a), b],
}, operators={})
PRELUDE_TYPE_CLASSES = {
'Eq': Eq,
'Ord': Ord,

View File

@ -186,7 +186,7 @@ class TupleMatchConstraint(ConstraintBase):
return [
SameTypeConstraint(arg, oth_arg)
for arg, oth_arg in zip(self.args, tp_args)
for arg, oth_arg in zip(self.args, tp_args, strict=True)
]
raise NotImplementedError(exp_type)
@ -241,14 +241,10 @@ class MustImplementTypeClassConstraint(ConstraintBase):
"""
__slots__ = ('type_class3', 'type3', )
type_class3: Union[str, typeclasses.Type3Class]
type_class3: typeclasses.Type3Class
type3: placeholders.Type3OrPlaceholder
DATA = {
'bytes': {'Foldable'},
}
def __init__(self, type_class3: Union[str, typeclasses.Type3Class], type3: placeholders.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
def __init__(self, type_class3: typeclasses.Type3Class, type3: placeholders.Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(comment=comment)
self.type_class3 = type_class3
@ -262,12 +258,8 @@ class MustImplementTypeClassConstraint(ConstraintBase):
if isinstance(typ, placeholders.PlaceholderForType):
return RequireTypeSubstitutes()
if isinstance(self.type_class3, typeclasses.Type3Class):
if self.type_class3 in typ.classes:
return None
else:
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')
@ -369,11 +361,11 @@ class LiteralFitsConstraint(ConstraintBase):
res.extend(
LiteralFitsConstraint(x, y)
for x, y in zip(tp_args, self.literal.value)
for x, y in zip(tp_args, self.literal.value, strict=True)
)
res.extend(
SameTypeConstraint(x, y.type3)
for x, y in zip(tp_args, self.literal.value)
for x, y in zip(tp_args, self.literal.value, strict=True)
)
return res
@ -417,11 +409,11 @@ class LiteralFitsConstraint(ConstraintBase):
res.extend(
LiteralFitsConstraint(x, y)
for x, y in zip(st_args.values(), self.literal.value)
for x, y in zip(st_args.values(), self.literal.value, strict=True)
)
res.extend(
SameTypeConstraint(x_t, y.type3, comment=f'{self.literal.struct_name}.{x_n}')
for (x_n, x_t, ), y in zip(st_args.items(), self.literal.value)
for (x_n, x_t, ), y in zip(st_args.items(), self.literal.value, strict=True)
)
return res

View File

@ -6,8 +6,9 @@ The constraints solver can then try to resolve all constraints.
from typing import Generator, List
from .. import ourlang, prelude
from . import functions as functions
from . import placeholders as placeholders
from . import typeclasses as type3typeclasses
from . import typeclasses as typeclasses
from . import types as type3types
from .constraints import (
CanBeSubscriptedConstraint,
@ -55,81 +56,51 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
raise NotImplementedError(expression, inp, inp.operator)
if isinstance(inp, ourlang.BinaryOp):
type_var_map = {
x: placeholders.PlaceholderForType([])
for x in inp.operator.signature
if isinstance(x, type3typeclasses.TypeVariable)
}
if isinstance(inp, ourlang.BinaryOp) or isinstance(inp, ourlang.FunctionCall):
signature = inp.operator.signature if isinstance(inp, ourlang.BinaryOp) else inp.function.signature
arguments = [inp.left, inp.right] if isinstance(inp, ourlang.BinaryOp) else inp.arguments
yield from expression(ctx, inp.left)
yield from expression(ctx, inp.right)
for type_var in inp.operator.type3_class.args:
assert type_var in type_var_map # When can this happen?
yield MustImplementTypeClassConstraint(
inp.operator.type3_class,
type_var_map[type_var],
)
for sig_part, arg_expr in zip(inp.operator.signature, [inp.left, inp.right, inp]):
if isinstance(sig_part, type3typeclasses.TypeVariable):
yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3)
continue
if isinstance(sig_part, type3types.Type3):
yield SameTypeConstraint(sig_part, arg_expr.type3)
continue
raise NotImplementedError(sig_part)
return
if isinstance(inp, ourlang.FunctionCall):
if isinstance(inp.function, type3typeclasses.Type3ClassMethod):
# FIXME: Duplicate code with BinaryOp
func_name = f'({inp.operator.name})' if isinstance(inp, ourlang.BinaryOp) else inp.function.name
type_var_map = {
x: placeholders.PlaceholderForType([])
for x in inp.function.signature
if isinstance(x, type3typeclasses.TypeVariable)
for x in signature.args
if isinstance(x, functions.TypeVariable)
}
for call_arg in inp.arguments:
for call_arg in arguments:
yield from expression(ctx, call_arg)
for type_var in inp.function.type3_class.args:
for type_var, constraint_list in signature.context.constraints.items():
assert type_var in type_var_map # When can this happen?
for constraint in constraint_list:
if isinstance(constraint, functions.TypeVariableConstraint_TypeHasTypeClass):
yield MustImplementTypeClassConstraint(
inp.function.type3_class,
constraint.type_class3,
type_var_map[type_var],
)
continue
for sig_part, arg_expr in zip(inp.function.signature, inp.arguments + [inp]):
if isinstance(sig_part, type3typeclasses.TypeVariable):
yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3)
raise NotImplementedError(constraint)
for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [inp], strict=True)):
if arg_no == len(arguments):
comment = f'The type of a function call to {func_name} is the same as the type that the function returns'
else:
comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument'
if isinstance(sig_part, functions.TypeVariable):
yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3, comment=comment)
continue
if isinstance(sig_part, type3types.Type3):
yield SameTypeConstraint(sig_part, arg_expr.type3)
yield SameTypeConstraint(sig_part, arg_expr.type3, comment=comment)
continue
raise NotImplementedError(sig_part)
return
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
for fun_arg, call_arg in zip(inp.function.posonlyargs, inp.arguments):
yield from expression(ctx, call_arg)
yield SameTypeConstraint(fun_arg.type3, call_arg.type3,
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
if isinstance(inp, ourlang.TupleInstantiation):
r_type = []
for arg in inp.elements:
@ -161,16 +132,6 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
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
if isinstance(inp, ourlang.Fold):
yield from expression(ctx, inp.base)
yield from expression(ctx, inp.iter)
yield SameTypeConstraint(inp.func.posonlyargs[0].type3, inp.func.returns_type3, inp.base.type3, inp.type3,
comment='foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b')
yield MustImplementTypeClassConstraint('Foldable', inp.iter.type3)
return
raise NotImplementedError(expression, inp)
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:

67
phasm/type3/functions.py Normal file
View File

@ -0,0 +1,67 @@
from typing import TYPE_CHECKING, Any, Iterable, List, Union
if TYPE_CHECKING:
from .typeclasses import Type3Class
from .types import Type3
class TypeVariable:
"""
Types variable are used in function definition.
They are used in places where you don't know the exact type.
They are different from PlaceholderForType, as those are instanced
during type checking. These type variables are used solely in the
function's definition
"""
__slots__ = ('letter', )
letter: str
def __init__(self, letter: str) -> None:
assert len(letter) == 1, f'{letter} is not a valid type variable'
self.letter = letter
def __hash__(self) -> int:
return hash(self.letter)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariable):
raise NotImplementedError
return self.letter == other.letter
def __repr__(self) -> str:
return f'TypeVariable({repr(self.letter)})'
class TypeVariableConstraintBase:
__slots__ = ()
class TypeVariableConstraint_TypeHasTypeClass(TypeVariableConstraintBase):
__slots__ = ('type_class3', )
def __init__(self, type_class3: 'Type3Class') -> None:
self.type_class3 = type_class3
class TypeVariableContext:
__slots__ = ('constraints', )
constraints: dict[TypeVariable, list[TypeVariableConstraintBase]]
def __init__(self) -> None:
self.constraints = {}
def __copy__(self) -> 'TypeVariableContext':
result = TypeVariableContext()
result.constraints.update(self.constraints)
return result
class FunctionSignature:
__slots__ = ('context', 'args', )
context: TypeVariableContext
args: List[Union['Type3', TypeVariable]]
def __init__(self, context: TypeVariableContext, args: Iterable[Union['Type3', TypeVariable]]) -> None:
self.context = context.__copy__()
self.args = list(args)

View File

@ -1,5 +1,11 @@
from typing import Any, Dict, Iterable, List, Mapping, Optional, Union
from .functions import (
FunctionSignature,
TypeVariable,
TypeVariableConstraint_TypeHasTypeClass,
TypeVariableContext,
)
from .types import Type3, TypeConstructor, TypeConstructor_Struct
@ -24,6 +30,27 @@ class TypeVariable:
def __repr__(self) -> str:
return f'TypeVariable({repr(self.letter)})'
class TypeConstructorVariable:
__slots__ = ('letter', )
letter: str
def __init__(self, letter: str) -> None:
assert len(letter) == 1, f'{letter} is not a valid type variable'
self.letter = letter
def __hash__(self) -> int:
return hash(self.letter)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariable):
raise NotImplementedError
return self.letter == other.letter
def __repr__(self) -> str:
return f'TypeVariable({repr(self.letter)})'
class TypeReference:
__slots__ = ('name', )
@ -45,26 +72,27 @@ class TypeReference:
def __repr__(self) -> str:
return f'TypeReference({repr(self.name)})'
Signature = List[Union[Type3, TypeVariable, list[TypeVariable]]]
class Type3ClassMethod:
__slots__ = ('type3_class', 'name', 'signature', )
__slots__ = ('name', 'signature', )
type3_class: 'Type3Class'
name: str
signature: List[Union[Type3, TypeVariable]]
signature: FunctionSignature
def __init__(self, name: str, signature: FunctionSignature) -> None:
def __init__(self, type3_class: 'Type3Class', name: str, signature: Iterable[Union[Type3, TypeVariable]]) -> None:
self.type3_class = type3_class
self.name = name
self.signature = list(signature)
self.signature = signature
def __repr__(self) -> str:
return f'Type3ClassMethod({repr(self.type3_class)}, {repr(self.name)}, {repr(self.signature)})'
return f'Type3ClassMethod({repr(self.name)}, {repr(self.signature)})'
class Type3Class:
__slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', )
name: str
args: List[TypeVariable]
args: List[Union[TypeVariable, TypeConstructorVariable]]
methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod]
inherited_classes: List['Type3Class']
@ -72,19 +100,33 @@ class Type3Class:
def __init__(
self,
name: str,
args: Iterable[TypeVariable],
methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]],
operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]],
args: Iterable[Union[TypeVariable, TypeConstructorVariable]],
methods: Mapping[str, Signature],
operators: Mapping[str, Signature],
inherited_classes: Optional[List['Type3Class']] = None,
) -> None:
self.name = name
self.args = list(args)
context = TypeVariableContext()
for arg in args:
context.constraints[arg] = [
TypeVariableConstraint_TypeHasTypeClass(self)
]
# FIXME: Multi parameter class types
# To fix this, realise that an instantiation of a multi paramater type class
# means that the instantiation depends on the combination of type classes
# and so we can't store the type classes on the types anymore
# This also means constraints should store a tuple of types as its key
assert len(context.constraints) <= 1
self.methods = {
k: Type3ClassMethod(self, k, v)
k: Type3ClassMethod(k, FunctionSignature(context, v))
for k, v in methods.items()
}
self.operators = {
k: Type3ClassMethod(self, k, v)
k: Type3ClassMethod(k, FunctionSignature(context, v))
for k, v in operators.items()
}
self.inherited_classes = inherited_classes or []

View File

@ -64,7 +64,7 @@ class Suite:
write_header(sys.stderr, 'Memory (pre alloc)')
runner.interpreter_dump_memory(sys.stderr)
for arg, arg_typ in zip(args, func_args):
for arg, arg_typ in zip(args, func_args, strict=True):
assert not isinstance(arg_typ, type3placeholders.PlaceholderForType), \
'Cannot call polymorphic function from outside'
@ -221,7 +221,7 @@ def _allocate_memory_stored_value(
assert len(val) == len(tp_args)
offset = adr
for val_el_val, val_el_typ in zip(val, tp_args):
for val_el_val, val_el_typ in zip(val, tp_args, strict=True):
assert not isinstance(val_el_typ, type3placeholders.PlaceholderForType)
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
@ -424,7 +424,7 @@ def _load_tuple_from_address(runner: runners.RunnerBase, typ_args: tuple[type3ty
return tuple(
_unpack(runner, arg_typ, arg_bytes)
for arg_typ, arg_bytes in zip(typ_args, _split_read_bytes(read_bytes, arg_sizes))
for arg_typ, arg_bytes in zip(typ_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
)
def _load_struct_from_address(runner: runners.RunnerBase, st_args: dict[str, type3types.Type3], adr: int) -> Any:
@ -444,5 +444,5 @@ def _load_struct_from_address(runner: runners.RunnerBase, st_args: dict[str, typ
return {
arg_name: _unpack(runner, arg_typ, arg_bytes)
for arg_name, arg_typ, arg_bytes in zip(name_list, typ_list, _split_read_bytes(read_bytes, arg_sizes))
for arg_name, arg_typ, arg_bytes in zip(name_list, typ_list, _split_read_bytes(read_bytes, arg_sizes), strict=True)
}

View File

@ -282,7 +282,7 @@ if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_'):
elif TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be (u32, ) instead',
'The type of the value passed to argument x of function helper should match the type of that argument',
'The type of the value passed to argument 0 of function helper should match the type of that argument',
)
else:
expect_type_error(
@ -333,11 +333,11 @@ def testEntry() -> i32:
if TYPE_NAME.startswith('tuple_') or TYPE_NAME.startswith('static_array_') or TYPE_NAME.startswith('struct_'):
expect_type_error(
TYPE + ' must be (u32, ) instead',
'The type of the value passed to argument x of function helper should match the type of that argument',
'The type of the value passed to argument 0 of function helper should match the type of that argument',
)
else:
expect_type_error(
TYPE_NAME + ' must be (u32, ) instead',
'The type of the value passed to argument x of function helper should match the type of that argument',
'The type of the value passed to argument 0 of function helper should match the type of that argument',
)
```

View File

@ -1,27 +1,8 @@
import sys
import pytest
from ..helpers import Suite, write_header
from ..runners import RunnerWasmtime
from ..helpers import Suite
def setup_interpreter(phash_code: str) -> RunnerWasmtime:
runner = RunnerWasmtime(phash_code)
runner.parse()
runner.compile_ast()
runner.compile_wat()
runner.interpreter_setup()
runner.interpreter_load()
write_header(sys.stderr, 'Phasm')
runner.dump_phasm_code(sys.stderr)
write_header(sys.stderr, 'Assembly')
runner.dump_wasm_wat(sys.stderr)
return runner
@pytest.mark.integration_test
def test_foldl_1():
code_py = """

View File

@ -0,0 +1,20 @@
import pytest
from phasm.exceptions import StaticError
from ..helpers import Suite
@pytest.mark.integration_test
def test_must_give_type_annotations():
code_py = """
def is_equal(lft, rgt) -> bool:
return lft == rgt
def testEntry() -> bool:
return is_equal(5, 15)
"""
suite = Suite(code_py)
with pytest.raises(StaticError, match='Must give a argument type'):
suite.run_code()