First attempts

This commit is contained in:
Johan B.W. de Vries 2025-07-12 17:28:52 +02:00
parent 74a0d700f3
commit f6b4f7c20a
10 changed files with 356 additions and 305 deletions

View File

@ -6,10 +6,6 @@ Contains nothing but the explicit compiler builtins.
from typing import Any, Callable, NamedTuple, Type
from warnings import warn
from ..type3.functions import (
TypeConstructorVariable,
TypeVariable,
)
from ..type3.routers import (
NoRouteForTypeException,
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 . 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:
@ -141,12 +141,15 @@ def function(inp: ourlang.Function) -> str:
if inp.imported:
result += '@imported\n'
params = [type3(x) for x in expect_function_type(inp.type3)]
return_type = params.pop()
args = ', '.join(
f'{p.name}: {type3(p.type3)}'
for p in inp.posonlyargs
f'{p_name}: {p_type3}'
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:
result += ' pass\n'

View File

@ -21,6 +21,7 @@ from .type3.types import (
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Tuple,
expect_function_type,
)
from .wasm import (
WasmTypeFloat32,
@ -197,9 +198,9 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
return
if isinstance(inp, ourlang.VariableReference):
if isinstance(inp.variable, ourlang.FunctionParam):
wgn.add_statement('local.get', '${}'.format(inp.variable.name))
return
# if isinstance(inp.variable, ourlang.FunctionParam): # TODO
# wgn.add_statement('local.get', '${}'.format(inp.variable.name))
# return
if isinstance(inp.variable, ourlang.ModuleConstantDef):
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] = {}
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
if isinstance(type_var, Type3):
@ -252,7 +254,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
# FIXME: Duplicate code with BinaryOp
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
if isinstance(type_var, Type3):
@ -277,13 +280,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
return
if isinstance(inp.function, ourlang.FunctionParam):
assert isinstance(inp.function.type3.application.constructor, TypeConstructor_Function)
params = [
type3(mod, x)
for x in inp.function.type3.application.arguments
]
params = [type3(mod, x) for x in expect_function_type(inp.function.type3)]
result = params.pop()
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)
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:
"""
Compile: imported function
"""
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(
inp.imported,
inp.name,
inp.name,
[
function_argument(mod, x)
for x in inp.posonlyargs
],
type3(mod, inp.returns_type3)
params,
return_type,
)
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:
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(
inp.name,
inp.name if inp.exported else None,
[
function_argument(mod, x)
for x in inp.posonlyargs
],
params,
[
(k, v.wasm_type(), )
for k, v in wgn.locals.items()
],
type3(mod, inp.returns_type3),
return_type,
wgn.statements
)

View File

@ -4,9 +4,14 @@ Contains the syntax tree for ourlang
from typing import Dict, Iterable, List, Optional, Union
from .build.base import BuildBase
from .type3.functions import FunctionSignature, TypeVariableContext
from .type3.functions import TypeVariableContext
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:
@ -277,38 +282,50 @@ class Function:
"""
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
lineno: int
exported: bool
imported: Optional[str]
statements: List[Statement]
signature: FunctionSignature
returns_type3: Type3
posonlyargs: List[FunctionParam]
type3: Type3
param_names: list[str]
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.lineno = lineno
self.exported = False
self.imported = None
self.statements = []
self.signature = FunctionSignature(TypeVariableContext(), [])
self.returns_type3 = returns_type3
self.posonlyargs = []
self.type3 = type3
self.param_names = param_names
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:
"""
The definition for a struct
"""
__slots__ = ('struct_type3', 'lineno', )
__slots__ = ('struct_type3', 'const_type3', 'lineno', )
struct_type3: Type3
const_type3: Type3
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.const_type3 = const_type3
self.lineno = lineno
class StructConstructor(Function):
@ -322,18 +339,13 @@ class StructConstructor(Function):
struct_type3: Type3
def __init__(self, struct_type3: Type3) -> None:
super().__init__(f'@{struct_type3.name}@__init___@', -1, struct_type3)
def __init__(self, struct_type3: Type3, const_type3: Type3) -> None:
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:
self.posonlyargs.append(FunctionParam(mem, typ, ))
self.signature.args.append(typ)
self.signature.args.append(struct_type3)
self.struct_type3 = struct_type3
super().__init__(f'@{struct_type3.name}@__init___@', -1, const_type3, param_names)
class ModuleConstantDef:
"""

View File

@ -49,7 +49,7 @@ def phasm_parse(source: str) -> Module[Generator]:
our_visitor = OurVisitor(build)
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):
"""
@ -124,7 +124,7 @@ class OurVisitor[G]:
)
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
module.struct_definitions[res.struct_type3.name] = res
@ -156,24 +156,27 @@ class OurVisitor[G]:
raise NotImplementedError(f'{node} on Module')
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')
arg_names: list[str]
arg_types: list[Type3]
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)
arg_names.append(arg.arg)
arg_types.append(self.visit_type(module, arg.annotation))
# FIXME: Allow TypeVariable in the function signature
# This would also require FunctionParam to accept a placeholder
if node.returns is None: # Note: `-> None` would be a ast.Constant
_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.posonlyargs.append(FunctionParam(
arg.arg,
arg_type,
))
function = Function(
node.name,
node.lineno,
self.build.function(*arg_types),
arg_names,
)
_not_implemented(not node.args.vararg, 'FunctionDef.args.vararg')
_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.defaults, 'FunctionDef.args.defaults')
# Do stmts at the end so we have the return value
for decorator in node.decorator_list:
if isinstance(decorator, ast.Call):
if not isinstance(decorator.func, ast.Name):
@ -213,12 +214,6 @@ class OurVisitor[G]:
else:
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')
return function
@ -249,7 +244,14 @@ class OurVisitor[G]:
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:
if not isinstance(node.target, ast.Name):
@ -313,7 +315,7 @@ class OurVisitor[G]:
our_locals: OurLocals = {
x.name: x
for x in function.posonlyargs
for x in function.get_params()
}
for stmt in node.body:
@ -478,12 +480,12 @@ class OurVisitor[G]:
if not isinstance(node.func.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
func: Union[Function, FunctionParam, Type3ClassMethod]
func: Union[Function, Type3ClassMethod]
if node.func.id in module.methods:
func = module.methods[node.func.id]
elif node.func.id in our_locals:
func = our_locals[node.func.id]
# elif node.func.id in our_locals: # TODO
# func = our_locals[node.func.id]
else:
if node.func.id not in module.functions:
_raise_static_error(node, 'Call to undefined function')

View File

@ -20,13 +20,12 @@ from .constraints import (
from .functions import (
Constraint_TypeClassInstanceExists,
FunctionArgument,
FunctionSignature,
TypeVariable,
TypeVariableApplication_Unary,
TypeVariableContext,
)
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]
@ -49,26 +48,17 @@ def expression_binary_op(ctx: Context, inp: ourlang.BinaryOp, phft: PlaceholderF
return _expression_function_call(
ctx,
f'({inp.operator.name})',
inp.operator.signature,
inp.operator.type3,
[inp.left, inp.right],
inp,
phft,
)
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(
ctx,
inp.function.name,
signature,
inp.function.type3,
inp.arguments,
inp,
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:
yield SameTypeConstraint(
ctx,
ctx.build.function(*(x.type3 for x in inp.function.posonlyargs), inp.function.returns_type3),
inp.function.type3,
phft,
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(
ctx: Context,
func_name: str,
signature: FunctionSignature,
type3: Type3,
arguments: list[ourlang.Expression],
return_expr: ourlang.Expression,
return_phft: PlaceholderForType,
@ -96,6 +86,8 @@ def _expression_function_call(
A Binary operator functions pretty much the same as a function call
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
arg_placeholders = {
arg_expr: PlaceholderForType([arg_expr])
@ -115,82 +107,84 @@ def _expression_function_call(
# subsituted - that's done by arg_placeholders.
type_var_map = {
x: PlaceholderForType([])
for x in signature.args
for x in param_types
if isinstance(x, TypeVariable)
}
for constraint in signature.context.constraints:
if isinstance(constraint, Constraint_TypeClassInstanceExists):
yield MustImplementTypeClassConstraint(
ctx,
constraint.type_class3,
[type_var_map[x] for x in constraint.types],
)
continue
# for constraint in signature.context.constraints: # TODO
# if isinstance(constraint, Constraint_TypeClassInstanceExists):
# yield MustImplementTypeClassConstraint(
# ctx,
# constraint.type_class3,
# [type_var_map[x] for x in constraint.types],
# )
# continue
raise NotImplementedError(constraint)
# raise NotImplementedError(constraint)
func_var_map = {
x: PlaceholderForType([])
for x in signature.args
for x in param_types
if isinstance(x, FunctionArgument)
}
# If some of the function arguments are functions,
# we need to deal with those separately.
for sig_arg in signature.args:
if not isinstance(sig_arg, FunctionArgument):
continue
# TODO
# # If some of the function arguments are functions,
# # we need to deal with those separately.
# for sig_arg in param_types:
# if not isinstance(sig_arg, FunctionArgument):
# continue
# Ensure that for all type variables in the function
# there are also type variables available
for func_arg in sig_arg.args:
if isinstance(func_arg, Type3):
continue
# # Ensure that for all type variables in the function
# # there are also type variables available
# for func_arg in sig_arg.args:
# if isinstance(func_arg, Type3):
# continue
type_var_map.setdefault(func_arg, PlaceholderForType([]))
# type_var_map.setdefault(func_arg, PlaceholderForType([]))
yield SameFunctionArgumentConstraint(
ctx,
func_var_map[sig_arg],
sig_arg,
type_var_map,
comment=f'Ensure `{sig_arg.name}` matches in {signature}',
)
# yield SameFunctionArgumentConstraint(
# ctx,
# func_var_map[sig_arg],
# sig_arg,
# type_var_map,
# comment=f'Ensure `{sig_arg.name}` matches in {type}',
# )
# If some of the function arguments are type constructors,
# we need to deal with those separately.
# That is, given `foo :: t a -> a` we need to ensure
# that both a's are the same.
for sig_arg in signature.args:
if isinstance(sig_arg, Type3):
# Not a type variable at all
continue
# TODO
# # If some of the function arguments are type constructors,
# # we need to deal with those separately.
# # That is, given `foo :: t a -> a` we need to ensure
# # that both a's are the same.
# for sig_arg in param_types:
# if isinstance(sig_arg, Type3):
# # Not a type variable at all
# continue
if isinstance(sig_arg, FunctionArgument):
continue
# if isinstance(sig_arg, FunctionArgument):
# continue
if sig_arg.application.constructor is None:
# Not a type variable for a type constructor
continue
# if sig_arg.application.constructor is None:
# # Not a type variable for a type constructor
# continue
if not isinstance(sig_arg.application, TypeVariableApplication_Unary):
raise NotImplementedError(sig_arg.application)
# if not isinstance(sig_arg.application, TypeVariableApplication_Unary):
# raise NotImplementedError(sig_arg.application)
if sig_arg.application.arguments not in type_var_map:
# e.g., len :: t a -> u32
# i.e. "a" does not matter at all
continue
# if sig_arg.application.arguments not in type_var_map:
# # e.g., len :: t a -> u32
# # i.e. "a" does not matter at all
# continue
yield SameTypeArgumentConstraint(
ctx,
type_var_map[sig_arg],
type_var_map[sig_arg.application.arguments],
comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {signature}',
)
# yield SameTypeArgumentConstraint(
# ctx,
# type_var_map[sig_arg],
# type_var_map[sig_arg.application.arguments],
# comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {type3}',
# )
# 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):
comment = f'The type of a function call to {func_name} is the same as the type that the function returns'
else:
@ -320,7 +314,7 @@ def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement
# a ~ c => c := a
# a ~ a => ignore
yield SameTypeConstraint(ctx, fun.returns_type3, phft,
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')
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:
from .typeclasses import Type3Class
from .types import Type3
# if TYPE_CHECKING:
# from .typeclasses import Type3Class
# from .types import Type3
class TypeVariableApplication_Base[T: Hashable, S: Hashable]:
"""
Records the constructor and arguments used to create this type.
# class TypeVariableApplication_Base[T: Hashable, S: Hashable]:
# """
# Records the constructor and arguments used to create this type.
Nullary types, or types of kind *, have both arguments set to None.
"""
constructor: T
arguments: S
# Nullary types, or types of kind *, have both arguments set to None.
# """
# constructor: T
# arguments: S
def __init__(self, constructor: T, arguments: S) -> None:
self.constructor = constructor
self.arguments = arguments
# def __init__(self, constructor: T, arguments: S) -> None:
# self.constructor = constructor
# self.arguments = arguments
def __hash__(self) -> int:
return hash((self.constructor, self.arguments, ))
# def __hash__(self) -> int:
# return hash((self.constructor, self.arguments, ))
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariableApplication_Base):
raise NotImplementedError
# def __eq__(self, other: Any) -> bool:
# if not isinstance(other, TypeVariableApplication_Base):
# raise NotImplementedError
return (self.constructor == other.constructor # type: ignore[no-any-return]
and self.arguments == other.arguments)
# return (self.constructor == other.constructor # type: ignore[no-any-return]
# and self.arguments == other.arguments)
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
# def __repr__(self) -> str:
# return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
class TypeVariable:
"""
Types variable are used in function definition.
# class TypeVariable2:
# """
# 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__ = ('name', 'application', )
# 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__ = ('name', 'application', )
name: str
application: TypeVariableApplication_Base[Any, Any]
# name: str
# application: TypeVariableApplication_Base[Any, Any]
def __init__(self, name: str, application: TypeVariableApplication_Base[Any, Any]) -> None:
self.name = name
self.application = application
# def __init__(self, name: str, application: TypeVariableApplication_Base[Any, Any]) -> None:
# self.name = name
# self.application = application
def __hash__(self) -> int:
return hash((self.name, self.application, ))
# def __hash__(self) -> int:
# return hash((self.name, self.application, ))
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariable):
raise NotImplementedError
# def __eq__(self, other: Any) -> bool:
# if not isinstance(other, TypeVariable):
# raise NotImplementedError
return (self.name == other.name
and self.application == other.application)
# return (self.name == other.name
# and self.application == other.application)
def __repr__(self) -> str:
return f'TypeVariable({repr(self.name)})'
# def __repr__(self) -> str:
# return f'TypeVariable({repr(self.name)})'
class TypeVariableApplication_Nullary(TypeVariableApplication_Base[None, None]):
"""
For the type for this function argument it's not relevant if it was constructed.
"""
# class TypeVariableApplication_Nullary(TypeVariableApplication_Base[None, None]):
# """
# For the type for this function argument it's not relevant if it was constructed.
# """
def make_typevar(name: str) -> TypeVariable:
"""
Helper function to make a type variable for a non-constructed type.
"""
return TypeVariable(name, TypeVariableApplication_Nullary(None, None))
# def make_typevar(name: str) -> TypeVariable:
# """
# Helper function to make a type variable for a non-constructed type.
# """
# return TypeVariable(name, TypeVariableApplication_Nullary(None, None))
class TypeConstructorVariable:
"""
Types constructor variable are used in function definition.
# class TypeConstructorVariable:
# """
# Types constructor variable are used in function definition.
They are a lot like TypeVariable, except that they represent a
type constructor rather than a type directly.
# They are a lot like TypeVariable, except that they represent a
# type constructor rather than a type directly.
For now, we only have type constructor variables for kind
* -> *.
"""
__slots__ = ('name', )
# For now, we only have type constructor variables for kind
# * -> *.
# """
# __slots__ = ('name', )
name: str
# name: str
def __init__(self, name: str) -> None:
self.name = name
# def __init__(self, name: str) -> None:
# self.name = name
def __hash__(self) -> int:
return hash((self.name, ))
# def __hash__(self) -> int:
# return hash((self.name, ))
def __eq__(self, other: Any) -> bool:
if other is None:
return False
# def __eq__(self, other: Any) -> bool:
# if other is None:
# return False
if not isinstance(other, TypeConstructorVariable):
raise NotImplementedError(other)
# if not isinstance(other, TypeConstructorVariable):
# raise NotImplementedError(other)
return (self.name == other.name)
# return (self.name == other.name)
def __call__(self, tvar: TypeVariable) -> 'TypeVariable':
return TypeVariable(
self.name + ' ' + tvar.name,
TypeVariableApplication_Unary(self, tvar)
)
# def __call__(self, tvar: TypeVariable) -> 'TypeVariable':
# return TypeVariable(
# self.name + ' ' + tvar.name,
# TypeVariableApplication_Unary(self, tvar)
# )
def __repr__(self) -> str:
return f'TypeConstructorVariable({self.name!r})'
# def __repr__(self) -> str:
# return f'TypeConstructorVariable({self.name!r})'
class TypeVariableApplication_Unary(TypeVariableApplication_Base[TypeConstructorVariable, TypeVariable]):
"""
The type for this function argument should be constructed from a type constructor.
# class TypeVariableApplication_Unary(TypeVariableApplication_Base[TypeConstructorVariable, TypeVariable]):
# """
# 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:
__slots__ = ()
# class ConstraintBase:
# __slots__ = ()
class Constraint_TypeClassInstanceExists(ConstraintBase):
__slots__ = ('type_class3', 'types', )
# class Constraint_TypeClassInstanceExists(ConstraintBase):
# __slots__ = ('type_class3', 'types', )
type_class3: 'Type3Class'
types: list[TypeVariable]
# type_class3: 'Type3Class'
# types: list[TypeVariable]
def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None:
self.type_class3 = type_class3
self.types = list(types)
# def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None:
# self.type_class3 = type_class3
# self.types = list(types)
# Sanity check. AFAIK, if you have a multi-parameter type class,
# you can only add a constraint by supplying types for all variables
assert len(self.type_class3.args) == len(self.types)
# # Sanity check. AFAIK, if you have a multi-parameter type class,
# # you can only add a constraint by supplying types for all variables
# assert len(self.type_class3.args) == len(self.types)
def __str__(self) -> str:
return self.type_class3.name + ' ' + ' '.join(x.name for x in self.types)
# def __str__(self) -> str:
# return self.type_class3.name + ' ' + ' '.join(x.name for x in self.types)
def __repr__(self) -> str:
return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})'
# def __repr__(self) -> str:
# return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})'
class TypeVariableContext:
__slots__ = ('constraints', )
# class TypeVariableContext:
# __slots__ = ('constraints', )
constraints: list[ConstraintBase]
# constraints: list[ConstraintBase]
def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None:
self.constraints = list(constraints)
# def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None:
# self.constraints = list(constraints)
def __copy__(self) -> 'TypeVariableContext':
return TypeVariableContext(self.constraints)
# def __copy__(self) -> 'TypeVariableContext':
# return TypeVariableContext(self.constraints)
def __str__(self) -> str:
if not self.constraints:
return ''
# def __str__(self) -> str:
# if not self.constraints:
# return ''
return '(' + ', '.join(str(x) for x in self.constraints) + ') => '
# return '(' + ', '.join(str(x) for x in self.constraints) + ') => '
def __repr__(self) -> str:
return f'TypeVariableContext({self.constraints!r})'
# def __repr__(self) -> str:
# return f'TypeVariableContext({self.constraints!r})'
class FunctionArgument:
__slots__ = ('args', 'name', )
# class FunctionArgument:
# __slots__ = ('args', 'name', )
args: list[Type3 | TypeVariable]
name: str
# args: list[Type3 | TypeVariable]
# name: str
def __init__(self, args: list[Type3 | TypeVariable]) -> None:
self.args = args
# def __init__(self, args: list[Type3 | TypeVariable]) -> None:
# self.args = args
self.name = '(' + ' -> '.join(x.name for x in args) + ')'
# self.name = '(' + ' -> '.join(x.name for x in args) + ')'
class FunctionSignature:
__slots__ = ('context', 'args', )
# class FunctionSignature2:
# __slots__ = ('context', 'args', )
context: TypeVariableContext
args: List[Type3 | TypeVariable | FunctionArgument]
# context: TypeVariableContext
# args: List[Type3 | TypeVariable | FunctionArgument]
def __init__(self, context: TypeVariableContext, args: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]) -> None:
self.context = context.__copy__()
self.args = list(
FunctionArgument(x) if isinstance(x, list) else x
for x in args
)
# def __init__(self, context: TypeVariableContext, args: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]) -> None:
# self.context = context.__copy__()
# self.args = list(
# FunctionArgument(x) if isinstance(x, list) else x
# for x in args
# )
def __str__(self) -> str:
return str(self.context) + ' -> '.join(x.name for x in self.args)
# def __str__(self) -> str:
# return str(self.context) + ' -> '.join(x.name for x in self.args)
def __repr__(self) -> str:
return f'FunctionSignature({self.context!r}, {self.args!r})'
# def __repr__(self) -> str:
# 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 (
Constraint_TypeClassInstanceExists,
ConstraintBase,
FunctionSignature,
TypeConstructorVariable,
TypeVariable,
TypeVariableContext,
)
from .types import Type3
from .types import Type3, expect_function_type
class Type3ClassMethod:
__slots__ = ('name', 'signature', )
__slots__ = ('name', 'type3', )
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.signature = signature
self.type3 = type3
def __str__(self) -> str:
return f'{self.name} :: {self.signature}'
return f'{self.name} :: {self.type3}'
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]

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
"""
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):
"""
Sometimes you can have an int on the type level, e.g. when using static arrays
@ -282,6 +302,16 @@ class TypeConstructor_Function(TypeConstructor_TypeStar):
def make_name(self, key: Tuple[Type3, ...]) -> str:
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], ...]]):
"""
Constructs struct types
@ -308,3 +338,13 @@ class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]
class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]):
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:
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]
if len(func_args) != len(args):
param_types = type3types.expect_function_type(runner.phasm_ast.functions[func_name].type3)
param_types.pop()
if len(param_types) != len(args):
raise RuntimeError(f'Invalid number of args for {func_name}')
wasm_args: List[Union[float, int]] = []
@ -125,7 +126,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, 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)
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,
wasm_value: 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:
type_info = runner.phasm_ast.build.type_info_map[ret_type3.name]