Compare commits

..

No commits in common. "f6b4f7c20aadfe79add7e2996ddbc3291ea418dd" and "1a3bc19dce791faabcd7e78b76b716c57153cae7" have entirely different histories.

11 changed files with 305 additions and 439 deletions

22
TODO.md
View File

@ -22,25 +22,3 @@
- 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,6 +6,10 @@ 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, expect_function_type from .type3.types import Type3, TypeApplication_Struct
def phasm_render(inp: ourlang.Module[Any]) -> str: def phasm_render(inp: ourlang.Module[Any]) -> str:
@ -141,15 +141,12 @@ 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}: {p_type3}' f'{p.name}: {type3(p.type3)}'
for p_name, p_type3 in zip(inp.param_names, params, strict=True) for p in inp.posonlyargs
) )
result += f'def {inp.name}({args}) -> {return_type}:\n' result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n'
if inp.imported: if inp.imported:
result += ' pass\n' result += ' pass\n'

View File

@ -21,7 +21,6 @@ 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,
@ -198,9 +197,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): # TODO if isinstance(inp.variable, ourlang.FunctionParam):
# 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
@ -224,8 +223,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
type_var_map: dict[TypeVariable, Type3] = {} type_var_map: dict[TypeVariable, Type3] = {}
param_types = expect_function_type(inp.operator.type3) for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True):
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):
@ -254,8 +252,7 @@ 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 = {}
param_types = expect_function_type(inp.function.type3) for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True):
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):
@ -280,7 +277,13 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
return return
if isinstance(inp.function, ourlang.FunctionParam): if isinstance(inp.function, ourlang.FunctionParam):
params = [type3(mod, x) for x in expect_function_type(inp.function.type3)] assert isinstance(inp.function.type3.application.constructor, TypeConstructor_Function)
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))
@ -385,26 +388,27 @@ 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, [
return_type, function_argument(mod, x)
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:
@ -421,23 +425,18 @@ 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()
], ],
return_type, type3(mod, inp.returns_type3),
wgn.statements wgn.statements
) )

View File

@ -4,14 +4,9 @@ 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 TypeVariableContext from .type3.functions import FunctionSignature, TypeVariableContext
from .type3.typeclasses import Type3ClassMethod from .type3.typeclasses import Type3ClassMethod
from .type3.types import ( from .type3.types import Type3, TypeApplication_Struct
Type3,
TypeApplication_Struct,
expect_function_type,
expect_struct_type,
)
class Expression: class Expression:
@ -282,50 +277,38 @@ class Function:
""" """
A function processes input and produces output A function processes input and produces output
""" """
__slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'signature', 'type3', 'param_names', ) __slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'signature', 'returns_type3', 'posonlyargs', )
name: str name: str
lineno: int lineno: int
exported: bool exported: bool
imported: Optional[str] imported: Optional[str]
statements: List[Statement] statements: List[Statement]
type3: Type3 signature: FunctionSignature
param_names: list[str] returns_type3: Type3
posonlyargs: List[FunctionParam]
def __init__(self, name: str, lineno: int, type3: Type3, param_names: list[str]) -> None: def __init__(self, name: str, lineno: int, returns_type3: Type3) -> 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.type3 = type3 self.signature = FunctionSignature(TypeVariableContext(), [])
self.param_names = param_names self.returns_type3 = returns_type3
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', 'const_type3', 'lineno', ) __slots__ = ('struct_type3', 'lineno', )
struct_type3: Type3 struct_type3: Type3
const_type3: Type3
lineno: int lineno: int
def __init__(self, struct_type3: Type3, const_type3: Type3, lineno: int) -> None: def __init__(self, struct_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):
@ -339,13 +322,18 @@ class StructConstructor(Function):
struct_type3: Type3 struct_type3: Type3
def __init__(self, struct_type3: Type3, const_type3: Type3) -> None: def __init__(self, struct_type3: Type3) -> None:
struct_params = expect_struct_type(struct_type3) super().__init__(f'@{struct_type3.name}@__init___@', -1, struct_type3)
expect_function_type(const_type3)
param_names = [x[0] for x in struct_params] assert isinstance(struct_type3.application, TypeApplication_Struct)
super().__init__(f'@{struct_type3.name}@__init___@', -1, const_type3, param_names) for mem, typ in struct_type3.application.arguments:
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, FunctionParam] OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict?
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, res.const_type3) module.functions[res.struct_type3.name] = StructConstructor(res.struct_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,27 +156,24 @@ 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_names.append(arg.arg) arg_type = self.visit_type(module, arg.annotation)
arg_types.append(self.visit_type(module, arg.annotation))
if node.returns is None: # Note: `-> None` would be a ast.Constant # FIXME: Allow TypeVariable in the function signature
_raise_static_error(node, 'Must give a return type') # This would also require FunctionParam to accept a placeholder
arg_types.append(self.visit_type(module, node.returns))
function = Function( function.signature.args.append(arg_type)
node.name, function.posonlyargs.append(FunctionParam(
node.lineno, arg.arg,
self.build.function(*arg_types), arg_type,
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')
@ -184,6 +181,8 @@ 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):
@ -214,6 +213,12 @@ 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
@ -244,14 +249,7 @@ class OurVisitor[G]:
members[stmt.target.id] = self.visit_type(module, stmt.annotation) members[stmt.target.id] = self.visit_type(module, stmt.annotation)
struct_type3 = module.build.struct(node.name, tuple(members.items())) return StructDefinition(module.build.struct(node.name, tuple(members.items())), node.lineno)
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):
@ -315,7 +313,7 @@ class OurVisitor[G]:
our_locals: OurLocals = { our_locals: OurLocals = {
x.name: x x.name: x
for x in function.get_params() for x in function.posonlyargs
} }
for stmt in node.body: for stmt in node.body:
@ -480,12 +478,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, Type3ClassMethod] func: Union[Function, FunctionParam, 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: # TODO elif node.func.id in our_locals:
# 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,12 +20,13 @@ 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, expect_function_type from .types import Type3, TypeApplication_Struct, TypeConstructor_Function
ConstraintGenerator = Generator[ConstraintBase, None, None] ConstraintGenerator = Generator[ConstraintBase, None, None]
@ -48,17 +49,26 @@ 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.type3, inp.operator.signature,
[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,
inp.function.type3, signature,
inp.arguments, inp.arguments,
inp, inp,
phft, phft,
@ -67,7 +77,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,
inp.function.type3, ctx.build.function(*(x.type3 for x in inp.function.posonlyargs), inp.function.returns_type3),
phft, phft,
comment=f'typeOf("{inp.function.name}") == typeOf({inp.function.name})', comment=f'typeOf("{inp.function.name}") == typeOf({inp.function.name})',
) )
@ -75,7 +85,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,
type3: Type3, signature: FunctionSignature,
arguments: list[ourlang.Expression], arguments: list[ourlang.Expression],
return_expr: ourlang.Expression, return_expr: ourlang.Expression,
return_phft: PlaceholderForType, return_phft: PlaceholderForType,
@ -86,8 +96,6 @@ 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])
@ -107,84 +115,82 @@ 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 param_types for x in signature.args
if isinstance(x, TypeVariable) if isinstance(x, TypeVariable)
} }
# for constraint in signature.context.constraints: # TODO for constraint in signature.context.constraints:
# 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 param_types for x in signature.args
if isinstance(x, FunctionArgument) if isinstance(x, FunctionArgument)
} }
# TODO # If some of the function arguments are functions,
# # If some of the function arguments are functions, # we need to deal with those separately.
# # we need to deal with those separately. for sig_arg in signature.args:
# for sig_arg in param_types: if not isinstance(sig_arg, FunctionArgument):
# if not isinstance(sig_arg, FunctionArgument): continue
# 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 {type}', comment=f'Ensure `{sig_arg.name}` matches in {signature}',
# ) )
# TODO # If some of the function arguments are type constructors,
# # If some of the function arguments are type constructors, # we need to deal with those separately.
# # we need to deal with those separately. # That is, given `foo :: t a -> a` we need to ensure
# # That is, given `foo :: t a -> a` we need to ensure # that both a's are the same.
# # that both a's are the same. for sig_arg in signature.args:
# for sig_arg in param_types: if isinstance(sig_arg, Type3):
# if isinstance(sig_arg, Type3): # Not a type variable at all
# # Not a type variable at all continue
# 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 {type3}', comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {signature}',
# ) )
# Lastly, tie the signature and expression together # Lastly, tie the signature and expression together
for arg_no, (sig_part, arg_expr) in enumerate(zip(param_types, arguments + [return_expr], strict=True)): for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, 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:
@ -273,48 +279,7 @@ 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)
# callme :: (a -> b) -> a -> b :: * yield SameTypeConstraint(ctx, fun.returns_type3, phft,
# 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 TypeVariable2: class TypeVariable:
# """ """
# 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 FunctionSignature2: class FunctionSignature:
# __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,30 +3,29 @@ 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, expect_function_type from .types import Type3
class Type3ClassMethod: class Type3ClassMethod:
__slots__ = ('name', 'type3', ) __slots__ = ('name', 'signature', )
name: str name: str
type3: Type3 signature: FunctionSignature
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.type3 = type3 self.signature = signature
def __str__(self) -> str: def __str__(self) -> str:
return f'{self.name} :: {self.type3}' return f'{self.name} :: {self.signature}'
def __repr__(self) -> str: def __repr__(self) -> str:
return f'Type3ClassMethod({repr(self.name)}, {repr(self.type3)})' return f'Type3ClassMethod({repr(self.name)}, {repr(self.signature)})'
Type3ClassArgs = tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable] Type3ClassArgs = tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable]

View File

@ -97,26 +97,6 @@ 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
@ -263,26 +243,6 @@ 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:
@ -302,16 +262,6 @@ 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
@ -338,13 +288,3 @@ 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,9 +115,8 @@ 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
param_types = type3types.expect_function_type(runner.phasm_ast.functions[func_name].type3) func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs]
param_types.pop() if len(func_args) != len(args):
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]] = []
@ -126,7 +125,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, param_types, strict=True): for arg, arg_typ in zip(args, func_args, 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):
@ -288,8 +287,7 @@ def _load_memory_stored_returned_value(
func_name: str, func_name: str,
wasm_value: Any, wasm_value: Any,
) -> Any: ) -> Any:
param_types = type3types.expect_function_type(runner.phasm_ast.functions[func_name].type3) ret_type3 = runner.phasm_ast.functions[func_name].returns_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]