Compare commits

...

2 Commits

Author SHA1 Message Date
Johan B.W. de Vries
f6b4f7c20a First attempts 2025-07-12 17:28:52 +02:00
Johan B.W. de Vries
74a0d700f3 Notes 2025-07-12 11:31:05 +02:00
11 changed files with 439 additions and 305 deletions

22
TODO.md
View File

@ -22,3 +22,25 @@
- Try to implement the min and max functions using select - Try to implement the min and max functions using select
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm - Read https://bytecodealliance.org/articles/multi-value-all-the-wasm
- GRose :: (* -> *) -> * -> *
- skolem => variable that cannot be unified
Limitations (for now):
- no type level nats
- only support first order kinds
Do not support yet:
```
data Record f = Record {
field: f Int
}
Record :: (* -> *) -> *
```
(Nested arrows)
- only support rank 1 types
```
mapRecord :: forall f g. (forall a. f a -> f b) -> Record f -> Record g
```
(Nested forall)

View File

@ -6,10 +6,6 @@ Contains nothing but the explicit compiler builtins.
from typing import Any, Callable, NamedTuple, Type from typing import Any, Callable, NamedTuple, Type
from warnings import warn from warnings import warn
from ..type3.functions import (
TypeConstructorVariable,
TypeVariable,
)
from ..type3.routers import ( from ..type3.routers import (
NoRouteForTypeException, NoRouteForTypeException,
TypeApplicationRouter, TypeApplicationRouter,

View File

@ -6,7 +6,7 @@ It's intented to be a "any color, as long as it's black" kind of renderer
from typing import Any, Generator from typing import Any, Generator
from . import ourlang from . import ourlang
from .type3.types import Type3, TypeApplication_Struct from .type3.types import Type3, TypeApplication_Struct, expect_function_type
def phasm_render(inp: ourlang.Module[Any]) -> str: def phasm_render(inp: ourlang.Module[Any]) -> str:
@ -141,12 +141,15 @@ def function(inp: ourlang.Function) -> str:
if inp.imported: if inp.imported:
result += '@imported\n' result += '@imported\n'
params = [type3(x) for x in expect_function_type(inp.type3)]
return_type = params.pop()
args = ', '.join( args = ', '.join(
f'{p.name}: {type3(p.type3)}' f'{p_name}: {p_type3}'
for p in inp.posonlyargs for p_name, p_type3 in zip(inp.param_names, params, strict=True)
) )
result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n' result += f'def {inp.name}({args}) -> {return_type}:\n'
if inp.imported: if inp.imported:
result += ' pass\n' result += ' pass\n'

View File

@ -21,6 +21,7 @@ from .type3.types import (
TypeConstructor_Function, TypeConstructor_Function,
TypeConstructor_StaticArray, TypeConstructor_StaticArray,
TypeConstructor_Tuple, TypeConstructor_Tuple,
expect_function_type,
) )
from .wasm import ( from .wasm import (
WasmTypeFloat32, WasmTypeFloat32,
@ -197,9 +198,9 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
return return
if isinstance(inp, ourlang.VariableReference): if isinstance(inp, ourlang.VariableReference):
if isinstance(inp.variable, ourlang.FunctionParam): # if isinstance(inp.variable, ourlang.FunctionParam): # TODO
wgn.add_statement('local.get', '${}'.format(inp.variable.name)) # wgn.add_statement('local.get', '${}'.format(inp.variable.name))
return # return
if isinstance(inp.variable, ourlang.ModuleConstantDef): if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert inp.type3 is not None, TYPE3_ASSERTION_ERROR assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
@ -223,7 +224,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
type_var_map: dict[TypeVariable, Type3] = {} type_var_map: dict[TypeVariable, Type3] = {}
for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True): param_types = expect_function_type(inp.operator.type3)
for type_var, arg_expr in zip(param_types, [inp.left, inp.right, inp], strict=True):
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
if isinstance(type_var, Type3): if isinstance(type_var, Type3):
@ -252,7 +254,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
# FIXME: Duplicate code with BinaryOp # FIXME: Duplicate code with BinaryOp
type_var_map = {} type_var_map = {}
for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True): param_types = expect_function_type(inp.function.type3)
for type_var, arg_expr in zip(param_types, inp.arguments + [inp], strict=True):
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
if isinstance(type_var, Type3): if isinstance(type_var, Type3):
@ -277,13 +280,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
return return
if isinstance(inp.function, ourlang.FunctionParam): if isinstance(inp.function, ourlang.FunctionParam):
assert isinstance(inp.function.type3.application.constructor, TypeConstructor_Function) params = [type3(mod, x) for x in expect_function_type(inp.function.type3)]
params = [
type3(mod, x)
for x in inp.function.type3.application.arguments
]
result = params.pop() result = params.pop()
wgn.add_statement('local.get', '${}'.format(inp.function.name)) wgn.add_statement('local.get', '${}'.format(inp.function.name))
@ -388,27 +385,26 @@ def statement(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourla
raise NotImplementedError(statement, inp) raise NotImplementedError(statement, inp)
def function_argument(mod: ourlang.Module[WasmGenerator], inp: ourlang.FunctionParam) -> wasm.Param:
"""
Compile: function argument
"""
return (inp.name, type3(mod, inp.type3), )
def import_(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Import: def import_(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Import:
""" """
Compile: imported function Compile: imported function
""" """
assert inp.imported assert inp.imported
param_types = [type3(mod, x) for x in expect_function_type(inp.type3)]
return_type = param_types.pop()
params = [
(p_name, p_type)
for p_name, p_type in zip(inp.param_names, param_types, strict=True)
]
return wasm.Import( return wasm.Import(
inp.imported, inp.imported,
inp.name, inp.name,
inp.name, inp.name,
[ params,
function_argument(mod, x) return_type,
for x in inp.posonlyargs
],
type3(mod, inp.returns_type3)
) )
def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Function: def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Function:
@ -425,18 +421,23 @@ def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.
for stat in inp.statements: for stat in inp.statements:
statement(wgn, mod, inp, stat) statement(wgn, mod, inp, stat)
param_types = [type3(mod, x) for x in expect_function_type(inp.type3)]
return_type = param_types.pop()
params = [
(p_name, p_type)
for p_name, p_type in zip(inp.param_names, param_types, strict=True)
]
return wasm.Function( return wasm.Function(
inp.name, inp.name,
inp.name if inp.exported else None, inp.name if inp.exported else None,
[ params,
function_argument(mod, x)
for x in inp.posonlyargs
],
[ [
(k, v.wasm_type(), ) (k, v.wasm_type(), )
for k, v in wgn.locals.items() for k, v in wgn.locals.items()
], ],
type3(mod, inp.returns_type3), return_type,
wgn.statements wgn.statements
) )

View File

@ -4,9 +4,14 @@ Contains the syntax tree for ourlang
from typing import Dict, Iterable, List, Optional, Union from typing import Dict, Iterable, List, Optional, Union
from .build.base import BuildBase from .build.base import BuildBase
from .type3.functions import FunctionSignature, TypeVariableContext from .type3.functions import TypeVariableContext
from .type3.typeclasses import Type3ClassMethod from .type3.typeclasses import Type3ClassMethod
from .type3.types import Type3, TypeApplication_Struct from .type3.types import (
Type3,
TypeApplication_Struct,
expect_function_type,
expect_struct_type,
)
class Expression: class Expression:
@ -277,38 +282,50 @@ class Function:
""" """
A function processes input and produces output A function processes input and produces output
""" """
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'signature', 'returns_type3', 'posonlyargs', ) __slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'signature', 'type3', 'param_names', )
name: str name: str
lineno: int lineno: int
exported: bool exported: bool
imported: Optional[str] imported: Optional[str]
statements: List[Statement] statements: List[Statement]
signature: FunctionSignature type3: Type3
returns_type3: Type3 param_names: list[str]
posonlyargs: List[FunctionParam]
def __init__(self, name: str, lineno: int, returns_type3: Type3) -> None: def __init__(self, name: str, lineno: int, type3: Type3, param_names: list[str]) -> None:
self.name = name self.name = name
self.lineno = lineno self.lineno = lineno
self.exported = False self.exported = False
self.imported = None self.imported = None
self.statements = [] self.statements = []
self.signature = FunctionSignature(TypeVariableContext(), []) self.type3 = type3
self.returns_type3 = returns_type3 self.param_names = param_names
self.posonlyargs = []
def get_params(self) -> list[FunctionParam]:
param_types = list(expect_function_type(self.type3))
param_types.pop()
return [
FunctionParam(p_name, p_type)
for p_name, p_type in zip(self.param_names, param_types)
]
class StructDefinition: class StructDefinition:
""" """
The definition for a struct The definition for a struct
""" """
__slots__ = ('struct_type3', 'lineno', ) __slots__ = ('struct_type3', 'const_type3', 'lineno', )
struct_type3: Type3 struct_type3: Type3
const_type3: Type3
lineno: int lineno: int
def __init__(self, struct_type3: Type3, lineno: int) -> None: def __init__(self, struct_type3: Type3, const_type3: Type3, lineno: int) -> None:
expect_struct_type(struct_type3)
expect_function_type(const_type3)
self.struct_type3 = struct_type3 self.struct_type3 = struct_type3
self.const_type3 = const_type3
self.lineno = lineno self.lineno = lineno
class StructConstructor(Function): class StructConstructor(Function):
@ -322,18 +339,13 @@ class StructConstructor(Function):
struct_type3: Type3 struct_type3: Type3
def __init__(self, struct_type3: Type3) -> None: def __init__(self, struct_type3: Type3, const_type3: Type3) -> None:
super().__init__(f'@{struct_type3.name}@__init___@', -1, struct_type3) struct_params = expect_struct_type(struct_type3)
expect_function_type(const_type3)
assert isinstance(struct_type3.application, TypeApplication_Struct) param_names = [x[0] for x in struct_params]
for mem, typ in struct_type3.application.arguments: super().__init__(f'@{struct_type3.name}@__init___@', -1, const_type3, param_names)
self.posonlyargs.append(FunctionParam(mem, typ, ))
self.signature.args.append(typ)
self.signature.args.append(struct_type3)
self.struct_type3 = struct_type3
class ModuleConstantDef: class ModuleConstantDef:
""" """

View File

@ -49,7 +49,7 @@ def phasm_parse(source: str) -> Module[Generator]:
our_visitor = OurVisitor(build) our_visitor = OurVisitor(build)
return our_visitor.visit_Module(res) return our_visitor.visit_Module(res)
OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict? OurLocals = Dict[str, FunctionParam]
class OptimizerTransformer(ast.NodeTransformer): class OptimizerTransformer(ast.NodeTransformer):
""" """
@ -124,7 +124,7 @@ class OurVisitor[G]:
) )
module.types[res.struct_type3.name] = res.struct_type3 module.types[res.struct_type3.name] = res.struct_type3
module.functions[res.struct_type3.name] = StructConstructor(res.struct_type3) module.functions[res.struct_type3.name] = StructConstructor(res.struct_type3, res.const_type3)
# Store that the definition was done in this module for the formatter # Store that the definition was done in this module for the formatter
module.struct_definitions[res.struct_type3.name] = res module.struct_definitions[res.struct_type3.name] = res
@ -156,24 +156,27 @@ class OurVisitor[G]:
raise NotImplementedError(f'{node} on Module') raise NotImplementedError(f'{node} on Module')
def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function: def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function:
function = Function(node.name, node.lineno, self.build.none_)
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs') _not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
arg_names: list[str]
arg_types: list[Type3]
for arg in node.args.args: for arg in node.args.args:
if arg.annotation is None: if arg.annotation is None:
_raise_static_error(node, 'Must give a argument type') _raise_static_error(node, 'Must give a argument type')
arg_type = self.visit_type(module, arg.annotation) arg_names.append(arg.arg)
arg_types.append(self.visit_type(module, arg.annotation))
# FIXME: Allow TypeVariable in the function signature if node.returns is None: # Note: `-> None` would be a ast.Constant
# This would also require FunctionParam to accept a placeholder _raise_static_error(node, 'Must give a return type')
arg_types.append(self.visit_type(module, node.returns))
function.signature.args.append(arg_type) function = Function(
function.posonlyargs.append(FunctionParam( node.name,
arg.arg, node.lineno,
arg_type, self.build.function(*arg_types),
)) arg_names,
)
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg') _not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
_not_implemented(not node.args.kwonlyargs, 'FunctionDef.args.kwonlyargs') _not_implemented(not node.args.kwonlyargs, 'FunctionDef.args.kwonlyargs')
@ -181,8 +184,6 @@ class OurVisitor[G]:
_not_implemented(not node.args.kwarg, 'FunctionDef.args.kwarg') _not_implemented(not node.args.kwarg, 'FunctionDef.args.kwarg')
_not_implemented(not node.args.defaults, 'FunctionDef.args.defaults') _not_implemented(not node.args.defaults, 'FunctionDef.args.defaults')
# Do stmts at the end so we have the return value
for decorator in node.decorator_list: for decorator in node.decorator_list:
if isinstance(decorator, ast.Call): if isinstance(decorator, ast.Call):
if not isinstance(decorator.func, ast.Name): if not isinstance(decorator.func, ast.Name):
@ -213,12 +214,6 @@ class OurVisitor[G]:
else: else:
function.imported = 'imports' function.imported = 'imports'
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') _not_implemented(not node.type_comment, 'FunctionDef.type_comment')
return function return function
@ -249,7 +244,14 @@ class OurVisitor[G]:
members[stmt.target.id] = self.visit_type(module, stmt.annotation) members[stmt.target.id] = self.visit_type(module, stmt.annotation)
return StructDefinition(module.build.struct(node.name, tuple(members.items())), node.lineno) struct_type3 = module.build.struct(node.name, tuple(members.items()))
const_type3 = module.build.function(*members.values(), struct_type3)
return StructDefinition(
const_type3,
struct_type3,
node.lineno,
)
def pre_visit_Module_AnnAssign(self, module: Module[G], node: ast.AnnAssign) -> ModuleConstantDef: def pre_visit_Module_AnnAssign(self, module: Module[G], node: ast.AnnAssign) -> ModuleConstantDef:
if not isinstance(node.target, ast.Name): if not isinstance(node.target, ast.Name):
@ -313,7 +315,7 @@ class OurVisitor[G]:
our_locals: OurLocals = { our_locals: OurLocals = {
x.name: x x.name: x
for x in function.posonlyargs for x in function.get_params()
} }
for stmt in node.body: for stmt in node.body:
@ -478,12 +480,12 @@ class OurVisitor[G]:
if not isinstance(node.func.ctx, ast.Load): if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
func: Union[Function, FunctionParam, Type3ClassMethod] func: Union[Function, Type3ClassMethod]
if node.func.id in module.methods: if node.func.id in module.methods:
func = module.methods[node.func.id] func = module.methods[node.func.id]
elif node.func.id in our_locals: # elif node.func.id in our_locals: # TODO
func = our_locals[node.func.id] # func = our_locals[node.func.id]
else: else:
if node.func.id not in module.functions: if node.func.id not in module.functions:
_raise_static_error(node, 'Call to undefined function') _raise_static_error(node, 'Call to undefined function')

View File

@ -20,13 +20,12 @@ from .constraints import (
from .functions import ( from .functions import (
Constraint_TypeClassInstanceExists, Constraint_TypeClassInstanceExists,
FunctionArgument, FunctionArgument,
FunctionSignature,
TypeVariable, TypeVariable,
TypeVariableApplication_Unary, TypeVariableApplication_Unary,
TypeVariableContext, TypeVariableContext,
) )
from .placeholders import PlaceholderForType from .placeholders import PlaceholderForType
from .types import Type3, TypeApplication_Struct, TypeConstructor_Function from .types import Type3, TypeApplication_Struct, TypeConstructor_Function, expect_function_type
ConstraintGenerator = Generator[ConstraintBase, None, None] ConstraintGenerator = Generator[ConstraintBase, None, None]
@ -49,26 +48,17 @@ def expression_binary_op(ctx: Context, inp: ourlang.BinaryOp, phft: PlaceholderF
return _expression_function_call( return _expression_function_call(
ctx, ctx,
f'({inp.operator.name})', f'({inp.operator.name})',
inp.operator.signature, inp.operator.type3,
[inp.left, inp.right], [inp.left, inp.right],
inp, inp,
phft, phft,
) )
def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: PlaceholderForType) -> ConstraintGenerator: def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp.function, ourlang.FunctionParam):
assert isinstance(inp.function.type3.application.constructor, TypeConstructor_Function)
signature = FunctionSignature(
TypeVariableContext(),
inp.function.type3.application.arguments,
)
else:
signature = inp.function.signature
return _expression_function_call( return _expression_function_call(
ctx, ctx,
inp.function.name, inp.function.name,
signature, inp.function.type3,
inp.arguments, inp.arguments,
inp, inp,
phft, phft,
@ -77,7 +67,7 @@ def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: Plac
def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: PlaceholderForType) -> ConstraintGenerator: def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: PlaceholderForType) -> ConstraintGenerator:
yield SameTypeConstraint( yield SameTypeConstraint(
ctx, ctx,
ctx.build.function(*(x.type3 for x in inp.function.posonlyargs), inp.function.returns_type3), inp.function.type3,
phft, phft,
comment=f'typeOf("{inp.function.name}") == typeOf({inp.function.name})', comment=f'typeOf("{inp.function.name}") == typeOf({inp.function.name})',
) )
@ -85,7 +75,7 @@ def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference,
def _expression_function_call( def _expression_function_call(
ctx: Context, ctx: Context,
func_name: str, func_name: str,
signature: FunctionSignature, type3: Type3,
arguments: list[ourlang.Expression], arguments: list[ourlang.Expression],
return_expr: ourlang.Expression, return_expr: ourlang.Expression,
return_phft: PlaceholderForType, return_phft: PlaceholderForType,
@ -96,6 +86,8 @@ def _expression_function_call(
A Binary operator functions pretty much the same as a function call A Binary operator functions pretty much the same as a function call
with two arguments - it's only a syntactic difference. with two arguments - it's only a syntactic difference.
""" """
param_types = expect_function_type(type3)
# First create placeholders for all arguments, and generate their constraints # First create placeholders for all arguments, and generate their constraints
arg_placeholders = { arg_placeholders = {
arg_expr: PlaceholderForType([arg_expr]) arg_expr: PlaceholderForType([arg_expr])
@ -115,82 +107,84 @@ def _expression_function_call(
# subsituted - that's done by arg_placeholders. # subsituted - that's done by arg_placeholders.
type_var_map = { type_var_map = {
x: PlaceholderForType([]) x: PlaceholderForType([])
for x in signature.args for x in param_types
if isinstance(x, TypeVariable) if isinstance(x, TypeVariable)
} }
for constraint in signature.context.constraints: # for constraint in signature.context.constraints: # TODO
if isinstance(constraint, Constraint_TypeClassInstanceExists): # if isinstance(constraint, Constraint_TypeClassInstanceExists):
yield MustImplementTypeClassConstraint( # yield MustImplementTypeClassConstraint(
ctx, # ctx,
constraint.type_class3, # constraint.type_class3,
[type_var_map[x] for x in constraint.types], # [type_var_map[x] for x in constraint.types],
) # )
continue # continue
raise NotImplementedError(constraint) # raise NotImplementedError(constraint)
func_var_map = { func_var_map = {
x: PlaceholderForType([]) x: PlaceholderForType([])
for x in signature.args for x in param_types
if isinstance(x, FunctionArgument) if isinstance(x, FunctionArgument)
} }
# If some of the function arguments are functions, # TODO
# we need to deal with those separately. # # If some of the function arguments are functions,
for sig_arg in signature.args: # # we need to deal with those separately.
if not isinstance(sig_arg, FunctionArgument): # for sig_arg in param_types:
continue # if not isinstance(sig_arg, FunctionArgument):
# continue
# Ensure that for all type variables in the function # # Ensure that for all type variables in the function
# there are also type variables available # # there are also type variables available
for func_arg in sig_arg.args: # for func_arg in sig_arg.args:
if isinstance(func_arg, Type3): # if isinstance(func_arg, Type3):
continue # continue
type_var_map.setdefault(func_arg, PlaceholderForType([])) # type_var_map.setdefault(func_arg, PlaceholderForType([]))
yield SameFunctionArgumentConstraint( # yield SameFunctionArgumentConstraint(
ctx, # ctx,
func_var_map[sig_arg], # func_var_map[sig_arg],
sig_arg, # sig_arg,
type_var_map, # type_var_map,
comment=f'Ensure `{sig_arg.name}` matches in {signature}', # comment=f'Ensure `{sig_arg.name}` matches in {type}',
) # )
# If some of the function arguments are type constructors, # TODO
# we need to deal with those separately. # # If some of the function arguments are type constructors,
# That is, given `foo :: t a -> a` we need to ensure # # we need to deal with those separately.
# that both a's are the same. # # That is, given `foo :: t a -> a` we need to ensure
for sig_arg in signature.args: # # that both a's are the same.
if isinstance(sig_arg, Type3): # for sig_arg in param_types:
# Not a type variable at all # if isinstance(sig_arg, Type3):
continue # # Not a type variable at all
# continue
if isinstance(sig_arg, FunctionArgument): # if isinstance(sig_arg, FunctionArgument):
continue # continue
if sig_arg.application.constructor is None: # if sig_arg.application.constructor is None:
# Not a type variable for a type constructor # # Not a type variable for a type constructor
continue # continue
if not isinstance(sig_arg.application, TypeVariableApplication_Unary): # if not isinstance(sig_arg.application, TypeVariableApplication_Unary):
raise NotImplementedError(sig_arg.application) # raise NotImplementedError(sig_arg.application)
if sig_arg.application.arguments not in type_var_map: # if sig_arg.application.arguments not in type_var_map:
# e.g., len :: t a -> u32 # # e.g., len :: t a -> u32
# i.e. "a" does not matter at all # # i.e. "a" does not matter at all
continue # continue
yield SameTypeArgumentConstraint( # yield SameTypeArgumentConstraint(
ctx, # ctx,
type_var_map[sig_arg], # type_var_map[sig_arg],
type_var_map[sig_arg.application.arguments], # type_var_map[sig_arg.application.arguments],
comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {signature}', # comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {type3}',
) # )
# Lastly, tie the signature and expression together # Lastly, tie the signature and expression together
for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [return_expr], strict=True)): for arg_no, (sig_part, arg_expr) in enumerate(zip(param_types, arguments + [return_expr], strict=True)):
if arg_no == len(arguments): 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' comment = f'The type of a function call to {func_name} is the same as the type that the function returns'
else: else:
@ -279,7 +273,48 @@ def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement
yield from expression(ctx, inp.value, phft) yield from expression(ctx, inp.value, phft)
yield SameTypeConstraint(ctx, fun.returns_type3, phft, # callme :: (a -> b) -> a -> b :: *
# def callme(fun: (a -> b), arg: a) -> b:
# return fun(arg)
# todouble :: u32 -> f32 :: *
# def todouble(in: u32) -> f32:
# ....
# def main():
# print(callme(todouble, 15))
# dynamic_array :: * -> *
# static_array :: Int -> * -> *
#
# class Foldable f
#
# instance Foldable (static_array n)
#
# def foo(r: u32[4], l: u32[...]) -> ...
#
#
# def foo(in: a[4]) -> a[4]:
# def min(lft: a, rgt: a) -> a:
# if lft < rgt:
# return lft
# return rgt
#
# min :: NatNum a => a -> a -> a
#
# main :: IO
# main = do
# show $ min 3 4
# show $ min 3.1 3.2
# a ~ b => b := a
# a ~ c => c := a
# a ~ a => ignore
yield SameTypeConstraint(ctx, fun.type3.application.arguments[-1], phft,
comment=f'The type of the value returned from function {fun.name} should match its return type') comment=f'The type of the value returned from function {fun.name} should match its return type')
def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator: def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:

View File

@ -1,194 +1,194 @@
from __future__ import annotations # from __future__ import annotations
from typing import TYPE_CHECKING, Any, Hashable, Iterable, List # from typing import TYPE_CHECKING, Any, Hashable, Iterable, List
if TYPE_CHECKING: # if TYPE_CHECKING:
from .typeclasses import Type3Class # from .typeclasses import Type3Class
from .types import Type3 # from .types import Type3
class TypeVariableApplication_Base[T: Hashable, S: Hashable]: # class TypeVariableApplication_Base[T: Hashable, S: Hashable]:
""" # """
Records the constructor and arguments used to create this type. # Records the constructor and arguments used to create this type.
Nullary types, or types of kind *, have both arguments set to None. # Nullary types, or types of kind *, have both arguments set to None.
""" # """
constructor: T # constructor: T
arguments: S # arguments: S
def __init__(self, constructor: T, arguments: S) -> None: # def __init__(self, constructor: T, arguments: S) -> None:
self.constructor = constructor # self.constructor = constructor
self.arguments = arguments # self.arguments = arguments
def __hash__(self) -> int: # def __hash__(self) -> int:
return hash((self.constructor, self.arguments, )) # return hash((self.constructor, self.arguments, ))
def __eq__(self, other: Any) -> bool: # def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariableApplication_Base): # if not isinstance(other, TypeVariableApplication_Base):
raise NotImplementedError # raise NotImplementedError
return (self.constructor == other.constructor # type: ignore[no-any-return] # return (self.constructor == other.constructor # type: ignore[no-any-return]
and self.arguments == other.arguments) # and self.arguments == other.arguments)
def __repr__(self) -> str: # def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})' # return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
class TypeVariable: # class TypeVariable2:
""" # """
Types variable are used in function definition. # Types variable are used in function definition.
They are used in places where you don't know the exact type. # They are used in places where you don't know the exact type.
They are different from PlaceholderForType, as those are instanced # They are different from PlaceholderForType, as those are instanced
during type checking. These type variables are used solely in the # during type checking. These type variables are used solely in the
function's definition # function's definition
""" # """
__slots__ = ('name', 'application', ) # __slots__ = ('name', 'application', )
name: str # name: str
application: TypeVariableApplication_Base[Any, Any] # application: TypeVariableApplication_Base[Any, Any]
def __init__(self, name: str, application: TypeVariableApplication_Base[Any, Any]) -> None: # def __init__(self, name: str, application: TypeVariableApplication_Base[Any, Any]) -> None:
self.name = name # self.name = name
self.application = application # self.application = application
def __hash__(self) -> int: # def __hash__(self) -> int:
return hash((self.name, self.application, )) # return hash((self.name, self.application, ))
def __eq__(self, other: Any) -> bool: # def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariable): # if not isinstance(other, TypeVariable):
raise NotImplementedError # raise NotImplementedError
return (self.name == other.name # return (self.name == other.name
and self.application == other.application) # and self.application == other.application)
def __repr__(self) -> str: # def __repr__(self) -> str:
return f'TypeVariable({repr(self.name)})' # return f'TypeVariable({repr(self.name)})'
class TypeVariableApplication_Nullary(TypeVariableApplication_Base[None, None]): # class TypeVariableApplication_Nullary(TypeVariableApplication_Base[None, None]):
""" # """
For the type for this function argument it's not relevant if it was constructed. # For the type for this function argument it's not relevant if it was constructed.
""" # """
def make_typevar(name: str) -> TypeVariable: # def make_typevar(name: str) -> TypeVariable:
""" # """
Helper function to make a type variable for a non-constructed type. # Helper function to make a type variable for a non-constructed type.
""" # """
return TypeVariable(name, TypeVariableApplication_Nullary(None, None)) # return TypeVariable(name, TypeVariableApplication_Nullary(None, None))
class TypeConstructorVariable: # class TypeConstructorVariable:
""" # """
Types constructor variable are used in function definition. # Types constructor variable are used in function definition.
They are a lot like TypeVariable, except that they represent a # They are a lot like TypeVariable, except that they represent a
type constructor rather than a type directly. # type constructor rather than a type directly.
For now, we only have type constructor variables for kind # For now, we only have type constructor variables for kind
* -> *. # * -> *.
""" # """
__slots__ = ('name', ) # __slots__ = ('name', )
name: str # name: str
def __init__(self, name: str) -> None: # def __init__(self, name: str) -> None:
self.name = name # self.name = name
def __hash__(self) -> int: # def __hash__(self) -> int:
return hash((self.name, )) # return hash((self.name, ))
def __eq__(self, other: Any) -> bool: # def __eq__(self, other: Any) -> bool:
if other is None: # if other is None:
return False # return False
if not isinstance(other, TypeConstructorVariable): # if not isinstance(other, TypeConstructorVariable):
raise NotImplementedError(other) # raise NotImplementedError(other)
return (self.name == other.name) # return (self.name == other.name)
def __call__(self, tvar: TypeVariable) -> 'TypeVariable': # def __call__(self, tvar: TypeVariable) -> 'TypeVariable':
return TypeVariable( # return TypeVariable(
self.name + ' ' + tvar.name, # self.name + ' ' + tvar.name,
TypeVariableApplication_Unary(self, tvar) # TypeVariableApplication_Unary(self, tvar)
) # )
def __repr__(self) -> str: # def __repr__(self) -> str:
return f'TypeConstructorVariable({self.name!r})' # return f'TypeConstructorVariable({self.name!r})'
class TypeVariableApplication_Unary(TypeVariableApplication_Base[TypeConstructorVariable, TypeVariable]): # class TypeVariableApplication_Unary(TypeVariableApplication_Base[TypeConstructorVariable, TypeVariable]):
""" # """
The type for this function argument should be constructed from a type constructor. # The type for this function argument should be constructed from a type constructor.
And we need to know what construtor that was, since that's the one we support. # And we need to know what construtor that was, since that's the one we support.
""" # """
class ConstraintBase: # class ConstraintBase:
__slots__ = () # __slots__ = ()
class Constraint_TypeClassInstanceExists(ConstraintBase): # class Constraint_TypeClassInstanceExists(ConstraintBase):
__slots__ = ('type_class3', 'types', ) # __slots__ = ('type_class3', 'types', )
type_class3: 'Type3Class' # type_class3: 'Type3Class'
types: list[TypeVariable] # types: list[TypeVariable]
def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None: # def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None:
self.type_class3 = type_class3 # self.type_class3 = type_class3
self.types = list(types) # self.types = list(types)
# Sanity check. AFAIK, if you have a multi-parameter type class, # # Sanity check. AFAIK, if you have a multi-parameter type class,
# you can only add a constraint by supplying types for all variables # # you can only add a constraint by supplying types for all variables
assert len(self.type_class3.args) == len(self.types) # assert len(self.type_class3.args) == len(self.types)
def __str__(self) -> str: # def __str__(self) -> str:
return self.type_class3.name + ' ' + ' '.join(x.name for x in self.types) # return self.type_class3.name + ' ' + ' '.join(x.name for x in self.types)
def __repr__(self) -> str: # def __repr__(self) -> str:
return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})' # return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})'
class TypeVariableContext: # class TypeVariableContext:
__slots__ = ('constraints', ) # __slots__ = ('constraints', )
constraints: list[ConstraintBase] # constraints: list[ConstraintBase]
def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None: # def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None:
self.constraints = list(constraints) # self.constraints = list(constraints)
def __copy__(self) -> 'TypeVariableContext': # def __copy__(self) -> 'TypeVariableContext':
return TypeVariableContext(self.constraints) # return TypeVariableContext(self.constraints)
def __str__(self) -> str: # def __str__(self) -> str:
if not self.constraints: # if not self.constraints:
return '' # return ''
return '(' + ', '.join(str(x) for x in self.constraints) + ') => ' # return '(' + ', '.join(str(x) for x in self.constraints) + ') => '
def __repr__(self) -> str: # def __repr__(self) -> str:
return f'TypeVariableContext({self.constraints!r})' # return f'TypeVariableContext({self.constraints!r})'
class FunctionArgument: # class FunctionArgument:
__slots__ = ('args', 'name', ) # __slots__ = ('args', 'name', )
args: list[Type3 | TypeVariable] # args: list[Type3 | TypeVariable]
name: str # name: str
def __init__(self, args: list[Type3 | TypeVariable]) -> None: # def __init__(self, args: list[Type3 | TypeVariable]) -> None:
self.args = args # self.args = args
self.name = '(' + ' -> '.join(x.name for x in args) + ')' # self.name = '(' + ' -> '.join(x.name for x in args) + ')'
class FunctionSignature: # class FunctionSignature2:
__slots__ = ('context', 'args', ) # __slots__ = ('context', 'args', )
context: TypeVariableContext # context: TypeVariableContext
args: List[Type3 | TypeVariable | FunctionArgument] # args: List[Type3 | TypeVariable | FunctionArgument]
def __init__(self, context: TypeVariableContext, args: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]) -> None: # def __init__(self, context: TypeVariableContext, args: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]) -> None:
self.context = context.__copy__() # self.context = context.__copy__()
self.args = list( # self.args = list(
FunctionArgument(x) if isinstance(x, list) else x # FunctionArgument(x) if isinstance(x, list) else x
for x in args # for x in args
) # )
def __str__(self) -> str: # def __str__(self) -> str:
return str(self.context) + ' -> '.join(x.name for x in self.args) # return str(self.context) + ' -> '.join(x.name for x in self.args)
def __repr__(self) -> str: # def __repr__(self) -> str:
return f'FunctionSignature({self.context!r}, {self.args!r})' # return f'FunctionSignature({self.context!r}, {self.args!r})'

View File

@ -3,29 +3,30 @@ from typing import Dict, Iterable, List, Mapping, Optional
from .functions import ( from .functions import (
Constraint_TypeClassInstanceExists, Constraint_TypeClassInstanceExists,
ConstraintBase, ConstraintBase,
FunctionSignature,
TypeConstructorVariable, TypeConstructorVariable,
TypeVariable, TypeVariable,
TypeVariableContext, TypeVariableContext,
) )
from .types import Type3 from .types import Type3, expect_function_type
class Type3ClassMethod: class Type3ClassMethod:
__slots__ = ('name', 'signature', ) __slots__ = ('name', 'type3', )
name: str name: str
signature: FunctionSignature type3: Type3
def __init__(self, name: str, type3: Type3) -> None:
expect_function_type(type3)
def __init__(self, name: str, signature: FunctionSignature) -> None:
self.name = name self.name = name
self.signature = signature self.type3 = type3
def __str__(self) -> str: def __str__(self) -> str:
return f'{self.name} :: {self.signature}' return f'{self.name} :: {self.type3}'
def __repr__(self) -> str: def __repr__(self) -> str:
return f'Type3ClassMethod({repr(self.name)}, {repr(self.signature)})' return f'Type3ClassMethod({repr(self.name)}, {repr(self.type3)})'
Type3ClassArgs = tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable] Type3ClassArgs = tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable]

View File

@ -97,6 +97,26 @@ class TypeApplication_Nullary(TypeApplication_Base[None, None]):
There was no constructor used to create this type - it's a 'simple' type like u32 There was no constructor used to create this type - it's a 'simple' type like u32
""" """
def make_type(name: str) -> Type3:
"""
Make a type of kind *
"""
return Type3(name, TypeApplication_Nullary(None, None))
class TypeApplication_Variable(TypeApplication_Base[None, None]):
"""
We don't know what the actual type is, it's to be determined.
"""
def make_type_variable(name: str) -> Type3:
"""
Make a type variable
Type variables always have kind *. But you can construct a type
from a type constructor using a type variable.
"""
return Type3(name, TypeApplication_Variable(None, None))
class IntType3(KindArgument): class IntType3(KindArgument):
""" """
Sometimes you can have an int on the type level, e.g. when using static arrays Sometimes you can have an int on the type level, e.g. when using static arrays
@ -243,6 +263,26 @@ class TypeConstructor_TypeStar(TypeConstructor_Base[Tuple[Type3, ...]]):
class TypeApplication_TypeStar(TypeApplication_Base[TypeConstructor_TypeStar, Tuple[Type3, ...]]): class TypeApplication_TypeStar(TypeApplication_Base[TypeConstructor_TypeStar, Tuple[Type3, ...]]):
pass pass
# head :: [a] -> a
# (+) :: NatNum a => a -> a -> a
# instancen NatNum f32
# (+) :: wasm_f32_add
# inc1 :: NatNum a => a -> a
# inc1 = (+) 1
# data Type3
# | Type3KindS String None
# | Type3KindS_S String Type3
# | Type3KindS_S_S String Type3 Type3
# data TypeVariable3
# | TypeVariable3KindS String None
# | TypeVariable3KindS_S String Type3
# | TypeVariable3KindS_S_S String Type3 Type3
class TypeConstructor_DynamicArray(TypeConstructor_Type): class TypeConstructor_DynamicArray(TypeConstructor_Type):
def make_name(self, key: Tuple[Type3]) -> str: def make_name(self, key: Tuple[Type3]) -> str:
if 'u8' == key[0].name: if 'u8' == key[0].name:
@ -262,6 +302,16 @@ class TypeConstructor_Function(TypeConstructor_TypeStar):
def make_name(self, key: Tuple[Type3, ...]) -> str: def make_name(self, key: Tuple[Type3, ...]) -> str:
return 'Callable[' + ', '.join(x.name for x in key) + ']' return 'Callable[' + ', '.join(x.name for x in key) + ']'
def expect_function_type(typ: Type3) -> list[Type3]:
"""
Checks for a function type and returns its argument types.
Raises an AssertionError if typ is not a function type.
"""
assert isinstance(typ.application, TypeApplication_TypeStar)
assert isinstance(typ.application.constructor, TypeConstructor_Function)
return list(typ.application.arguments)
class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]): class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]):
""" """
Constructs struct types Constructs struct types
@ -288,3 +338,13 @@ class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]
class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]): class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]):
pass pass
def expect_struct_type(typ: Type3) -> list[tuple[str, Type3]]:
"""
Checks for a struct type and returns its argument types.
Raises an AssertionError if typ is not a struct type.
"""
assert isinstance(typ.application, TypeApplication_Struct)
assert isinstance(typ.application.constructor, TypeConstructor_Struct)
return list(typ.application.arguments)

View File

@ -115,8 +115,9 @@ class Suite:
if do_format_check: if do_format_check:
assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests
func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs] param_types = type3types.expect_function_type(runner.phasm_ast.functions[func_name].type3)
if len(func_args) != len(args): param_types.pop()
if len(param_types) != len(args):
raise RuntimeError(f'Invalid number of args for {func_name}') raise RuntimeError(f'Invalid number of args for {func_name}')
wasm_args: List[Union[float, int]] = [] wasm_args: List[Union[float, int]] = []
@ -125,7 +126,7 @@ class Suite:
write_header(sys.stderr, 'Memory (pre alloc)') write_header(sys.stderr, 'Memory (pre alloc)')
runner.interpreter_dump_memory(sys.stderr) runner.interpreter_dump_memory(sys.stderr)
for arg, arg_typ in zip(args, func_args, strict=True): for arg, arg_typ in zip(args, param_types, strict=True):
arg_typ_info = runner.phasm_ast.build.type_info_map.get(arg_typ.name) arg_typ_info = runner.phasm_ast.build.type_info_map.get(arg_typ.name)
if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeInt32 or arg_typ_info.wasm_type is WasmTypeInt64): if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeInt32 or arg_typ_info.wasm_type is WasmTypeInt64):
@ -287,7 +288,8 @@ def _load_memory_stored_returned_value(
func_name: str, func_name: str,
wasm_value: Any, wasm_value: Any,
) -> Any: ) -> Any:
ret_type3 = runner.phasm_ast.functions[func_name].returns_type3 param_types = type3types.expect_function_type(runner.phasm_ast.functions[func_name].type3)
ret_type3 = param_types[-1]
if ret_type3.name in runner.phasm_ast.build.type_info_map: if ret_type3.name in runner.phasm_ast.build.type_info_map:
type_info = runner.phasm_ast.build.type_info_map[ret_type3.name] type_info = runner.phasm_ast.build.type_info_map[ret_type3.name]