Compare commits

..

2 Commits

Author SHA1 Message Date
Johan B.W. de Vries
a3d9b8af0a Cleanup 2025-08-03 15:48:14 +02:00
Johan B.W. de Vries
07ffbbbaab Typeclasses 2025-08-03 12:37:50 +02:00
30 changed files with 566 additions and 2903 deletions

View File

@ -23,7 +23,7 @@ lint: venv/.done
venv/bin/ruff check phasm tests venv/bin/ruff check phasm tests
typecheck: venv/.done typecheck: venv/.done
venv/bin/mypy --strict phasm wat2wasm.py tests/integration/helpers.py tests/integration/runners.py venv/bin/mypy --strict phasm wat2wasm.py tests/integration/helpers.py tests/integration/memory.py tests/integration/runners.py
venv/.done: requirements.txt venv/.done: requirements.txt
python3.12 -m venv venv python3.12 -m venv venv

View File

@ -7,7 +7,7 @@ import sys
from .compiler import phasm_compile from .compiler import phasm_compile
from .optimise.removeunusedfuncs import removeunusedfuncs from .optimise.removeunusedfuncs import removeunusedfuncs
from .parser import phasm_parse from .parser import phasm_parse
from .type3.entry import phasm_type3 from .type5.solver import phasm_type5
def main(source: str, sink: str) -> int: def main(source: str, sink: str) -> int:
@ -19,7 +19,7 @@ def main(source: str, sink: str) -> int:
code_py = fil.read() code_py = fil.read()
our_module = phasm_parse(code_py) our_module = phasm_parse(code_py)
phasm_type3(our_module, verbose=False) phasm_type5(our_module, verbose=False)
wasm_module = phasm_compile(our_module) wasm_module = phasm_compile(our_module)
removeunusedfuncs(wasm_module) removeunusedfuncs(wasm_module)
code_wat = wasm_module.to_wat() code_wat = wasm_module.to_wat()

View File

@ -6,32 +6,11 @@ Contains nothing but the explicit compiler builtins.
from typing import Any, Callable, NamedTuple, Sequence, Type from typing import Any, Callable, NamedTuple, Sequence, Type
from warnings import warn from warnings import warn
from ..type3.functions import (
TypeConstructorVariable,
TypeVariable,
)
from ..type3.routers import (
NoRouteForTypeException,
TypeApplicationRouter,
TypeClassArgsRouter,
TypeVariableLookup,
)
from ..typeclass import TypeClass from ..typeclass import TypeClass
from ..type3.types import (
IntType3,
Type3,
TypeConstructor_Base,
TypeConstructor_DynamicArray,
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Struct,
TypeConstructor_Tuple,
)
from ..type5 import kindexpr as type5kindexpr from ..type5 import kindexpr as type5kindexpr
from ..type5 import record as type5record from ..type5 import record as type5record
from ..type5 import typeexpr as type5typeexpr from ..type5 import typeexpr as type5typeexpr
from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone
from . import builtins
from .typerouter import TypeAllocSize, TypeName from .typerouter import TypeAllocSize, TypeName
from .typevariablerouter import TypeVariableRouter from .typevariablerouter import TypeVariableRouter
@ -59,102 +38,105 @@ class MissingImplementationWarning(Warning):
class BuildBase[G]: class BuildBase[G]:
__slots__ = ( __slots__ = (
'dynamic_array',
'dynamic_array_type5_constructor', 'dynamic_array_type5_constructor',
'function',
'function_type5_constructor', 'function_type5_constructor',
'static_array',
'static_array_type5_constructor', 'static_array_type5_constructor',
'struct',
'tuple_',
'tuple_type5_constructor_map', 'tuple_type5_constructor_map',
'none_',
'none_type5', 'none_type5',
'unit_type5', 'unit_type5',
'bool_',
'bool_type5', 'bool_type5',
'u8_type5', 'u8_type5',
'u32_type5', 'u32_type5',
'bytes_type5',
'type_info_map', 'type_info_map',
'type_info_constructed', 'type_info_constructed',
'types', 'types',
'type5s',
'type_classes', 'type_classes',
'type_class_instances', 'type_class_instances',
'type_class_instance_methods',
'methods', 'methods',
'operators', 'operators',
'type5_name', 'type5_name',
'type5_alloc_size_root', 'type5_alloc_size_root',
'type5_alloc_size_member', 'type5_alloc_size_member',
'alloc_size_router',
) )
dynamic_array: TypeConstructor_DynamicArray
"""
This is a dynamic length piece of memory.
It should be applied with two arguments. It has a runtime
determined length, and each argument is the same.
"""
dynamic_array_type5_constructor: type5typeexpr.TypeConstructor dynamic_array_type5_constructor: type5typeexpr.TypeConstructor
function: TypeConstructor_Function
""" """
This is a function. Constructor for arrays of runtime deterined length.
It should be applied with one or more arguments. The last argument is the 'return' type. See type5_make_dynamic_array and type5_is_dynamic_array.
""" """
function_type5_constructor: type5typeexpr.TypeConstructor function_type5_constructor: type5typeexpr.TypeConstructor
static_array: TypeConstructor_StaticArray
""" """
This is a fixed length piece of memory. Constructor for functions.
It should be applied with two arguments. It has a compile time See type5_make_function and type5_is_function.
determined length, and each argument is the same.
""" """
static_array_type5_constructor: type5typeexpr.TypeConstructor static_array_type5_constructor: type5typeexpr.TypeConstructor
struct: TypeConstructor_Struct
"""
This is like a tuple, but each argument is named, so that developers
can get and set fields by name.
""" """
Constructor for arrays of compiled time determined length.
tuple_: TypeConstructor_Tuple See type5_make_static_array and type5_is_static_array.
"""
This is a fixed length piece of memory.
It should be applied with zero or more arguments. It has a compile time
determined length, and each argument can be different.
""" """
tuple_type5_constructor_map: dict[int, type5typeexpr.TypeConstructor] tuple_type5_constructor_map: dict[int, type5typeexpr.TypeConstructor]
"""
Map for constructors for tuples of each length.
none_: Type3 See type5_make_tuple and type5_is_tuple.
"""
The none type, for when functions simply don't return anything. e.g., IO().
""" """
none_type5: type5typeexpr.AtomicType none_type5: type5typeexpr.AtomicType
"""
The none type.
TODO: Not sure this should be a buildin (rather than a Maybe type).
"""
unit_type5: type5typeexpr.AtomicType unit_type5: type5typeexpr.AtomicType
bool_: Type3
""" """
The bool type, either True or False The unit type has exactly one value and can always be constructed.
Use for functions that don't take any arguments or do not produce any result.
This only make sense for IO functions.
TODO: Is this not what Python calls None?
""" """
bool_type5: type5typeexpr.AtomicType bool_type5: type5typeexpr.AtomicType
"""
The bool type, either True or False.
Builtin since functions require a boolean value in their test.
"""
u8_type5: type5typeexpr.AtomicType u8_type5: type5typeexpr.AtomicType
"""
The u8 type, an integer value between 0 and 255.
Builtin since we can have bytes literals - which are the same as u8[...].
"""
u32_type5: type5typeexpr.AtomicType u32_type5: type5typeexpr.AtomicType
"""
The u32 type, an integer value between 0 and 4 294 967 295.
Builtin since we can use this for indexing arrays and since
we use this for the length prefix on dynamic arrays.
"""
bytes_type5: type5typeexpr.TypeApplication
"""
The bytes type, a dynamic array with u8 elements.
Builtin since we can have bytes literals.
"""
type_info_map: dict[str, TypeInfo] type_info_map: dict[str, TypeInfo]
""" """
@ -170,12 +152,7 @@ class BuildBase[G]:
not memory pointers but table addresses instead. not memory pointers but table addresses instead.
""" """
types: dict[str, Type3] types: dict[str, type5typeexpr.TypeExpr]
"""
Types that are available without explicit import.
"""
type5s: dict[str, type5typeexpr.TypeExpr]
""" """
Types that are available without explicit import. Types that are available without explicit import.
""" """
@ -190,17 +167,12 @@ class BuildBase[G]:
Type class instances that are available without explicit import. Type class instances that are available without explicit import.
""" """
# type_class_instance_methods: dict[tuple[TypeClass, str], TypeClassArgsRouter[G, None]] methods: dict[str, tuple[type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr, TypeVariableRouter[G]]]
"""
Methods (and operators) for type class instances that are available without explicit import.
"""
methods: dict[str, tuple[type5typeexpr.TypeExpr, TypeVariableRouter[G]]]
""" """
Methods that are available without explicit import. Methods that are available without explicit import.
""" """
# operators: dict[str, Type3ClassMethod] operators: dict[str, tuple[type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr, TypeVariableRouter[G]]]
""" """
Operators that are available without explicit import. Operators that are available without explicit import.
""" """
@ -224,163 +196,68 @@ class BuildBase[G]:
This calculates the value when allocated as a member, e.g. in a tuple or struct. This calculates the value when allocated as a member, e.g. in a tuple or struct.
""" """
alloc_size_router: TypeApplicationRouter['BuildBase[G]', int]
"""
Helper value for calculate_alloc_size.
"""
def __init__(self) -> None: def __init__(self) -> None:
self.dynamic_array = builtins.dynamic_array
self.function = builtins.function
self.static_array = builtins.static_array
self.struct = builtins.struct
self.tuple_ = builtins.tuple_
S = type5kindexpr.Star() S = type5kindexpr.Star()
N = type5kindexpr.Nat() N = type5kindexpr.Nat()
self.function_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> (S >> S), name="function")
self.tuple_type5_constructor_map = {}
self.dynamic_array_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> S, name="dynamic_array") self.dynamic_array_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> S, name="dynamic_array")
self.function_type5_constructor = type5typeexpr.TypeConstructor(kind=S >> (S >> S), name="function")
self.static_array_type5_constructor = type5typeexpr.TypeConstructor(kind=N >> (S >> S), name='static_array') self.static_array_type5_constructor = type5typeexpr.TypeConstructor(kind=N >> (S >> S), name='static_array')
self.tuple_type5_constructor_map = {}
self.bool_ = builtins.bool_
self.bool_type5 = type5typeexpr.AtomicType('bool')
self.unit_type5 = type5typeexpr.AtomicType('()')
self.none_ = builtins.none_
self.none_type5 = type5typeexpr.AtomicType('None') self.none_type5 = type5typeexpr.AtomicType('None')
self.unit_type5 = type5typeexpr.AtomicType('()')
self.bool_type5 = type5typeexpr.AtomicType('bool')
self.u8_type5 = type5typeexpr.AtomicType('u8') self.u8_type5 = type5typeexpr.AtomicType('u8')
self.u32_type5 = type5typeexpr.AtomicType('u32') self.u32_type5 = type5typeexpr.AtomicType('u32')
self.bytes_type5 = self.type5_make_dynamic_array(self.u8_type5)
self.type_info_map = { self.type_info_map = {
'None': TypeInfo('ptr', WasmTypeNone, 'unreachable', 'unreachable', 0, None), 'None': TypeInfo('None', WasmTypeNone, 'unreachable', 'unreachable', 0, None),
'()': TypeInfo('()', WasmTypeInt32, 'unreachable', 'unreachable', 0, None),
'bool': TypeInfo('bool', WasmTypeInt32, 'unreachable', 'unreachable', 0, None), 'bool': TypeInfo('bool', WasmTypeInt32, 'unreachable', 'unreachable', 0, None),
'ptr': TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False), 'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, False),
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
} }
self.type_info_constructed = self.type_info_map['ptr'] self.type_info_constructed = TypeInfo('ptr', WasmTypeInt32, 'i32.load', 'i32.store', 4, False)
self.types = { self.types = {
'None': self.none_, 'None': self.none_type5,
'bool': self.bool_, '()': self.unit_type5,
}
self.type5s = {
'bool': self.bool_type5, 'bool': self.bool_type5,
'u8': self.u8_type5, 'u8': self.u8_type5,
'u32': self.u32_type5, 'u32': self.u32_type5,
'bytes': self.bytes_type5,
} }
self.type_classes = {} self.type_classes = {}
self.type_class_instances = {} self.type_class_instances = {}
self.type_class_instance_methods = {}
self.methods = {} self.methods = {}
self.operators = {} self.operators = {}
self.alloc_size_router = TypeApplicationRouter['BuildBase[G]', int]()
self.alloc_size_router.add(self.static_array, self.__class__.calculate_alloc_size_static_array)
self.alloc_size_router.add(self.struct, self.__class__.calculate_alloc_size_struct)
self.alloc_size_router.add(self.tuple_, self.__class__.calculate_alloc_size_tuple)
self.type5_name = TypeName(self) self.type5_name = TypeName(self)
self.type5_alloc_size_root = TypeAllocSize(self, is_member=False) self.type5_alloc_size_root = TypeAllocSize(self, is_member=False)
self.type5_alloc_size_member = TypeAllocSize(self, is_member=True) self.type5_alloc_size_member = TypeAllocSize(self, is_member=True)
# def register_type_class(self, cls: Type3Class) -> None:
# """
# Register that the given type class exists
# """
# old_len_methods = len(self.methods)
# old_len_operators = len(self.operators)
# self.type_classes[cls.name] = cls
# self.methods.update(cls.methods)
# self.operators.update(cls.operators)
# assert len(self.methods) == old_len_methods + len(cls.methods), 'Duplicated method detected'
# assert len(self.operators) == old_len_operators + len(cls.operators), 'Duplicated operator detected'
# def instance_type_class(
# self,
# cls: Type3Class,
# *typ: Type3 | TypeConstructor_Base[Any],
# methods: dict[str, Callable[[G, TypeVariableLookup], None]] = {},
# operators: dict[str, Callable[[G, TypeVariableLookup], None]] = {},
# ) -> None:
# """
# Registered the given type class and its implementation
# """
# assert len(cls.args) == len(typ)
# for incls in cls.inherited_classes:
# if (incls, tuple(typ), ) not in self.type_class_instances:
# warn(MissingImplementationWarning(
# incls.name + ' ' + ' '.join(x.name for x in typ) + ' - required for ' + cls.name
# ))
# # First just register the type
# self.type_class_instances.add((cls, tuple(typ), ))
# # Then make the implementation findable
# # We route based on the type class arguments.
# tv_map: dict[TypeVariable, Type3] = {}
# tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]] = {}
# for arg_tv, arg_tp in zip(cls.args, typ, strict=True):
# if isinstance(arg_tv, TypeVariable):
# assert isinstance(arg_tp, Type3)
# tv_map[arg_tv] = arg_tp
# elif isinstance(arg_tv, TypeConstructorVariable):
# assert isinstance(arg_tp, TypeConstructor_Base)
# tc_map[arg_tv] = arg_tp
# else:
# raise NotImplementedError(arg_tv, arg_tp)
# for method_name, method in cls.methods.items():
# router = self.type_class_instance_methods.get(method)
# if router is None:
# router = TypeClassArgsRouter[G, None](cls.args)
# self.type_class_instance_methods[method] = router
# try:
# generator = methods[method_name]
# except KeyError:
# warn(MissingImplementationWarning(str(method), cls.name + ' ' + ' '.join(x.name for x in typ)))
# continue
# router.add(tv_map, tc_map, generator)
# for operator_name, operator in cls.operators.items():
# router = self.type_class_instance_methods.get(operator)
# if router is None:
# router = TypeClassArgsRouter[G, None](cls.args)
# self.type_class_instance_methods[operator] = router
# try:
# generator = operators[operator_name]
# except KeyError:
# warn(MissingImplementationWarning(str(operator), cls.name + ' ' + ' '.join(x.name for x in typ)))
# continue
# router.add(tv_map, tc_map, generator)
def register_type_class(self, cls: TypeClass) -> None: def register_type_class(self, cls: TypeClass) -> None:
assert cls.name not in self.type_classes, 'Duplicate typeclass name' assert cls.name not in self.type_classes, 'Duplicate typeclass name'
self.type_classes[cls.name] = cls self.type_classes[cls.name] = cls
self.type_class_instances[cls.name] = set() self.type_class_instances[cls.name] = set()
def register_type_class_method( for mtd_nam, mtd_typ in cls.methods.items():
self, assert mtd_nam not in self.methods, 'Duplicate typeclass method name'
cls: TypeClass, self.methods[mtd_nam] = (mtd_typ, TypeVariableRouter(), )
name: str,
type: type5typeexpr.TypeExpr,
) -> None:
assert name not in self.methods, 'Duplicate typeclass method name'
self.methods[name] = (type, TypeVariableRouter(), ) for opr_nam, opr_typ in cls.operators.items():
assert opr_nam not in self.operators, 'Duplicate typeclass operator name'
self.operators[opr_nam] = (opr_typ, TypeVariableRouter(), )
def register_type_class_instance( def register_type_class_instance(
self, self,
cls: TypeClass, cls: TypeClass,
*args: type5typeexpr.TypeExpr, *args: type5typeexpr.TypeExpr,
methods: dict[str, Callable[[G, Any], None]], methods: dict[str, Callable[[G, Any], None]] = {},
operators: dict[str, Callable[[G, Any], None]] = {},
) -> None: ) -> None:
self.type_class_instances[cls.name].add(tuple(args)) self.type_class_instances[cls.name].add(tuple(args))
@ -390,61 +267,9 @@ class BuildBase[G]:
_, mtd_rtr = self.methods[mtd_nam] _, mtd_rtr = self.methods[mtd_nam]
mtd_rtr.register(cls.variables, args, mtd_imp) mtd_rtr.register(cls.variables, args, mtd_imp)
def calculate_alloc_size_static_array(self, args: tuple[Type3, IntType3]) -> int: for opr_nam, opr_imp in operators.items():
""" _, opr_rtr = self.operators[opr_nam]
Helper method for calculate_alloc_size - static_array opr_rtr.register(cls.variables, args, opr_imp)
"""
sa_type, sa_len = args
return sa_len.value * self.calculate_alloc_size(sa_type, is_member=True)
def calculate_alloc_size_tuple(self, args: tuple[Type3, ...]) -> int:
"""
Helper method for calculate_alloc_size - tuple
"""
return sum(
self.calculate_alloc_size(x, is_member=True)
for x in args
)
def calculate_alloc_size_struct(self, args: tuple[tuple[str, Type3], ...]) -> int:
"""
Helper method for calculate_alloc_size - struct
"""
return sum(
self.calculate_alloc_size(x, is_member=True)
for _, x in args
)
def calculate_alloc_size(self, typ: Type3, is_member: bool = False) -> int:
"""
Calculates how much bytes you need to allocate when reserving memory for the given type.
"""
typ_info = self.type_info_map.get(typ.name)
if typ_info is not None:
return typ_info.alloc_size
if is_member:
return self.type_info_constructed.alloc_size
try:
return self.alloc_size_router(self, typ)
except NoRouteForTypeException:
raise NotImplementedError(typ)
def type5_struct_offset(self, fields: tuple[tuple[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication], ...], needle: str) -> int:
"""
Calculates the amount of bytes that should be skipped in memory befor reaching the struct's property with the given name.
"""
result = 0
for memnam, memtyp in fields:
if needle == memnam:
return result
result += self.type5_alloc_size_member(memtyp)
raise RuntimeError('Member not found')
def type5_make_function(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeExpr: def type5_make_function(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeExpr:
if not args: if not args:
@ -476,7 +301,10 @@ class BuildBase[G]:
return res_type5 return res_type5
def type5_is_function(self, typeexpr: type5typeexpr.TypeExpr) -> list[type5typeexpr.TypeExpr] | None: def type5_is_function(self, typeexpr: type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr) -> list[type5typeexpr.TypeExpr] | None:
if isinstance(typeexpr, type5typeexpr.ConstrainedExpr):
typeexpr = typeexpr.expr
if not isinstance(typeexpr, type5typeexpr.TypeApplication): if not isinstance(typeexpr, type5typeexpr.TypeApplication):
return None return None
if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication): if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication):

View File

@ -1,26 +0,0 @@
"""
Type (constructors) that are used integral to the compiler.
These cannot be changed as there is compiler logic depending on them.
For mode documentation, see base.py.
"""
from ..type3.types import (
Type3,
TypeApplication_Nullary,
TypeConstructor_DynamicArray,
TypeConstructor_Function,
TypeConstructor_StaticArray,
TypeConstructor_Struct,
TypeConstructor_Tuple,
)
dynamic_array = TypeConstructor_DynamicArray('dynamic_array')
function = TypeConstructor_Function('function')
static_array = TypeConstructor_StaticArray('static_array')
struct = TypeConstructor_Struct('struct')
tuple_ = TypeConstructor_Tuple('tuple')
bool_ = Type3('bool', TypeApplication_Nullary(None, None))
none_ = Type3('None', TypeApplication_Nullary(None, None))

View File

@ -7,10 +7,6 @@ Contains the compiler builtins as well as some sane defaults.
f32: A 32-bits IEEE 754 float, of 32 bits width. f32: A 32-bits IEEE 754 float, of 32 bits width.
""" """
from ..type3.types import (
Type3,
TypeApplication_Nullary,
)
from ..type5 import typeexpr as type5typeexpr from ..type5 import typeexpr as type5typeexpr
from ..wasm import ( from ..wasm import (
WasmTypeFloat32, WasmTypeFloat32,
@ -21,21 +17,21 @@ from ..wasm import (
from ..wasmgenerator import Generator from ..wasmgenerator import Generator
from .base import BuildBase, TypeInfo from .base import BuildBase, TypeInfo
from .typeclasses import ( from .typeclasses import (
bits, # bits,
convertable, # convertable,
eq, eq,
extendable, # extendable,
floating, floating,
foldable, # foldable,
fractional, # fractional,
integral, # integral,
intnum, # intnum,
natnum, # natnum,
ord, # ord,
promotable, # promotable,
reinterpretable, # reinterpretable,
sized, # sized,
subscriptable, # subscriptable,
) )
@ -45,23 +41,8 @@ class BuildDefault(BuildBase[Generator]):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
u8 = Type3('u8', TypeApplication_Nullary(None, None))
u16 = Type3('u16', TypeApplication_Nullary(None, None))
u32 = Type3('u32', TypeApplication_Nullary(None, None))
u64 = Type3('u64', TypeApplication_Nullary(None, None))
i8 = Type3('i8', TypeApplication_Nullary(None, None))
i16 = Type3('i16', TypeApplication_Nullary(None, None))
i32 = Type3('i32', TypeApplication_Nullary(None, None))
i64 = Type3('i64', TypeApplication_Nullary(None, None))
f32 = Type3('f32', TypeApplication_Nullary(None, None))
f64 = Type3('f64', TypeApplication_Nullary(None, None))
bytes_ = self.dynamic_array(u8)
self.type_info_map.update({ self.type_info_map.update({
'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, False),
'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2, False), 'u16': TypeInfo('u16', WasmTypeInt32, 'i32.load16_u', 'i32.store16', 2, False),
'u32': TypeInfo('u32', WasmTypeInt32, 'i32.load', 'i32.store', 4, False),
'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False), 'u64': TypeInfo('u64', WasmTypeInt64, 'i64.load', 'i64.store', 8, False),
'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True), 'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True),
'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True), 'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True),
@ -72,20 +53,6 @@ class BuildDefault(BuildBase[Generator]):
}) })
self.types.update({ self.types.update({
'u8': u8,
'u16': u16,
'u32': u32,
'u64': u64,
'i8': i8,
'i16': i16,
'i32': i32,
'i64': i64,
'f32': f32,
'f64': f64,
'bytes': bytes_,
})
self.type5s.update({
'u16': type5typeexpr.AtomicType('u16'), 'u16': type5typeexpr.AtomicType('u16'),
'u64': type5typeexpr.AtomicType('u64'), 'u64': type5typeexpr.AtomicType('u64'),
'i8': type5typeexpr.AtomicType('i8'), 'i8': type5typeexpr.AtomicType('i8'),
@ -94,13 +61,12 @@ class BuildDefault(BuildBase[Generator]):
'i64': type5typeexpr.AtomicType('i64'), 'i64': type5typeexpr.AtomicType('i64'),
'f32': type5typeexpr.AtomicType('f32'), 'f32': type5typeexpr.AtomicType('f32'),
'f64': type5typeexpr.AtomicType('f64'), 'f64': type5typeexpr.AtomicType('f64'),
'bytes': type5typeexpr.TypeApplication(self.dynamic_array_type5_constructor, self.u8_type5),
}) })
tc_list = [ tc_list = [
floating, floating,
# bits, # bits,
# eq, ord, eq, # ord,
# extendable, promotable, # extendable, promotable,
# convertable, reinterpretable, # convertable, reinterpretable,
# natnum, intnum, fractional, floating, # natnum, intnum, fractional, floating,

View File

@ -1,23 +1,34 @@
""" """
The Eq type class is defined for types that can be compered based on equality. The Eq type class is defined for types that can be compered based on equality.
""" """
from __future__ import annotations
from typing import Any from typing import Any
from ...type3.functions import make_typevar from ...type5.kindexpr import Star
from ...type3.routers import TypeVariableLookup from ...type5.typeexpr import ConstrainedExpr, TypeVariable
from ...type3.typeclasses import Type3Class from ...typeclass import TypeClass, TypeClassConstraint
from ...wasmgenerator import Generator as WasmGenerator from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase from ..base import BuildBase
def load(build: BuildBase[Any]) -> None: def load(build: BuildBase[Any]) -> None:
a = make_typevar('a') a = TypeVariable(kind=Star(), name='a')
Eq = Type3Class('Eq', (a, ), methods={}, operators={ Eq = TypeClass('Eq', (a, ), methods={}, operators={})
'==': [a, a, build.bool_],
'!=': [a, a, build.bool_], has_a = TypeClassConstraint(Eq, [a])
fn_a_a_bool = ConstrainedExpr(
expr=build.type5_make_function([a, a, build.bool_type5]),
constraints=(has_a, ),
)
Eq.operators = {
'==': fn_a_a_bool,
'!=': fn_a_a_bool,
# FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization? # FIXME: Do we want to expose 'eqz'? Or is that a compiler optimization?
}) }
build.register_type_class(Eq) build.register_type_class(Eq)
@ -104,43 +115,43 @@ def wasm_f64_not_equals(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm(build: BuildBase[WasmGenerator]) -> None: def wasm(build: BuildBase[WasmGenerator]) -> None:
Eq = build.type_classes['Eq'] Eq = build.type_classes['Eq']
build.instance_type_class(Eq, build.types['u8'], operators={ build.register_type_class_instance(Eq, build.types['u8'], operators={
'==': wasm_u8_equals, '==': wasm_u8_equals,
'!=': wasm_u8_not_equals, '!=': wasm_u8_not_equals,
}) })
build.instance_type_class(Eq, build.types['u16'], operators={ build.register_type_class_instance(Eq, build.types['u16'], operators={
'==': wasm_u16_equals, '==': wasm_u16_equals,
'!=': wasm_u16_not_equals, '!=': wasm_u16_not_equals,
}) })
build.instance_type_class(Eq, build.types['u32'], operators={ build.register_type_class_instance(Eq, build.types['u32'], operators={
'==': wasm_u32_equals, '==': wasm_u32_equals,
'!=': wasm_u32_not_equals, '!=': wasm_u32_not_equals,
}) })
build.instance_type_class(Eq, build.types['u64'], operators={ build.register_type_class_instance(Eq, build.types['u64'], operators={
'==': wasm_u64_equals, '==': wasm_u64_equals,
'!=': wasm_u64_not_equals, '!=': wasm_u64_not_equals,
}) })
build.instance_type_class(Eq, build.types['i8'], operators={ build.register_type_class_instance(Eq, build.types['i8'], operators={
'==': wasm_i8_equals, '==': wasm_i8_equals,
'!=': wasm_i8_not_equals, '!=': wasm_i8_not_equals,
}) })
build.instance_type_class(Eq, build.types['i16'], operators={ build.register_type_class_instance(Eq, build.types['i16'], operators={
'==': wasm_i16_equals, '==': wasm_i16_equals,
'!=': wasm_i16_not_equals, '!=': wasm_i16_not_equals,
}) })
build.instance_type_class(Eq, build.types['i32'], operators={ build.register_type_class_instance(Eq, build.types['i32'], operators={
'==': wasm_i32_equals, '==': wasm_i32_equals,
'!=': wasm_i32_not_equals, '!=': wasm_i32_not_equals,
}) })
build.instance_type_class(Eq, build.types['i64'], operators={ build.register_type_class_instance(Eq, build.types['i64'], operators={
'==': wasm_i64_equals, '==': wasm_i64_equals,
'!=': wasm_i64_not_equals, '!=': wasm_i64_not_equals,
}) })
build.instance_type_class(Eq, build.types['f32'], operators={ build.register_type_class_instance(Eq, build.types['f32'], operators={
'==': wasm_f32_equals, '==': wasm_f32_equals,
'!=': wasm_f32_not_equals, '!=': wasm_f32_not_equals,
}) })
build.instance_type_class(Eq, build.types['f64'], operators={ build.register_type_class_instance(Eq, build.types['f64'], operators={
'==': wasm_f64_equals, '==': wasm_f64_equals,
'!=': wasm_f64_not_equals, '!=': wasm_f64_not_equals,
}) })

View File

@ -1,11 +1,12 @@
""" """
The Floating type class is defined for Real numbers. The Floating type class is defined for Real numbers.
""" """
from __future__ import annotations
from typing import Any from typing import Any
from ...type5.kindexpr import Star from ...type5.kindexpr import Star
from ...type5.typeexpr import TypeVariable from ...type5.typeexpr import TypeVariable
from ...type3.routers import TypeVariableLookup
from ...typeclass import TypeClass from ...typeclass import TypeClass
from ...wasmgenerator import Generator as WasmGenerator from ...wasmgenerator import Generator as WasmGenerator
from ..base import BuildBase from ..base import BuildBase
@ -14,18 +15,15 @@ from ..base import BuildBase
def load(build: BuildBase[Any]) -> None: def load(build: BuildBase[Any]) -> None:
a = TypeVariable(kind=Star(), name='a') a = TypeVariable(kind=Star(), name='a')
Floating = TypeClass('Floating', [a]) fn_a_a = build.type5_make_function([a, a])
Floating = TypeClass('Floating', [a], methods={
'sqrt': fn_a_a
})
build.register_type_class(Floating) build.register_type_class(Floating)
build.register_type_class_method(Floating, 'sqrt', build.type5_make_function([a, a])) # FIXME: inherited_classes=[Fractional]
# FIXME: Do we want to expose copysign?
# a = make_typevar('a')
# # Fractional = build.type_classes['Fractional'] # TODO
# Floating = Type3Class('Floating', (a, ), methods={
# 'sqrt': [a, a],
# }, operators={}, inherited_classes=[Fractional])
# # FIXME: Do we want to expose copysign?
def wasm_f32_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None: def wasm_f32_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
del tv_map del tv_map
@ -38,9 +36,9 @@ def wasm_f64_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None:
def wasm(build: BuildBase[WasmGenerator]) -> None: def wasm(build: BuildBase[WasmGenerator]) -> None:
Floating = build.type_classes['Floating'] Floating = build.type_classes['Floating']
build.register_type_class_instance(Floating, build.type5s['f32'], methods={ build.register_type_class_instance(Floating, build.types['f32'], methods={
'sqrt': wasm_f32_sqrt, 'sqrt': wasm_f32_sqrt,
}) })
build.register_type_class_instance(Floating, build.type5s['f64'], methods={ build.register_type_class_instance(Floating, build.types['f64'], methods={
'sqrt': wasm_f64_sqrt, 'sqrt': wasm_f64_sqrt,
}) })

View File

@ -1,4 +1,4 @@
from typing import Any, Callable, Iterable, TypeAlias from typing import Any, Callable, Iterable
from ..type5 import typeexpr as type5typeexpr from ..type5 import typeexpr as type5typeexpr
@ -25,7 +25,6 @@ class TypeVariableRouter[G]:
assert len(variables) == len(set(variables)) assert len(variables) == len(set(variables))
key = tuple(sorted(tuple(zip(variables, types)))) key = tuple(sorted(tuple(zip(variables, types))))
print('key', key)
self.data[key] = implementation self.data[key] = implementation
def __call__( def __call__(
@ -37,5 +36,4 @@ class TypeVariableRouter[G]:
types = list(types) types = list(types)
key = tuple(sorted(tuple(zip(variables, types)))) key = tuple(sorted(tuple(zip(variables, types))))
print('key', key)
return self.data[key] return self.data[key]

View File

@ -6,7 +6,6 @@ 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 .type5 import typeexpr as type5typeexpr from .type5 import typeexpr as type5typeexpr
@ -18,35 +17,27 @@ def phasm_render(inp: ourlang.Module[Any]) -> str:
Statements = Generator[str, None, None] Statements = Generator[str, None, None]
def type3(inp: Type3) -> str:
"""
Render: type's name
"""
return inp.name
def type5(mod: ourlang.Module[Any], inp: type5typeexpr.TypeExpr) -> str: def type5(mod: ourlang.Module[Any], inp: type5typeexpr.TypeExpr) -> str:
""" """
Render: type's name Render: type's name
""" """
return mod.build.type5_name(inp) return mod.build.type5_name(inp)
def struct_definition(inp: ourlang.StructDefinition) -> str: def struct_definition(mod: ourlang.Module[Any], inp: ourlang.StructDefinition) -> str:
""" """
Render: TypeStruct's definition Render: TypeStruct's definition
""" """
assert isinstance(inp.struct_type3.application, TypeApplication_Struct) result = f'class {inp.struct_type5.name}:\n'
for mem, typ in inp.struct_type5.fields:
result = f'class {inp.struct_type3.name}:\n' result += f' {mem}: {type5(mod, typ)}\n'
for mem, typ in inp.struct_type3.application.arguments:
result += f' {mem}: {type3(typ)}\n'
return result return result
def constant_definition(inp: ourlang.ModuleConstantDef) -> str: def constant_definition(mod: ourlang.Module[Any], inp: ourlang.ModuleConstantDef) -> str:
""" """
Render: Module Constant's definition Render: Module Constant's definition
""" """
return f'{inp.name}: {type3(inp.type3)} = {expression(inp.constant)}\n' return f'{inp.name}: {type5(mod, inp.type5)} = {expression(inp.constant)}\n'
def expression(inp: ourlang.Expression) -> str: def expression(inp: ourlang.Expression) -> str:
""" """
@ -67,7 +58,7 @@ def expression(inp: ourlang.Expression) -> str:
) + ', )' ) + ', )'
if isinstance(inp, ourlang.ConstantStruct): if isinstance(inp, ourlang.ConstantStruct):
return inp.struct_type3.name + '(' + ', '.join( return inp.struct_type5.name + '(' + ', '.join(
expression(x) expression(x)
for x in inp.value for x in inp.value
) + ')' ) + ')'
@ -76,7 +67,7 @@ def expression(inp: ourlang.Expression) -> str:
return str(inp.variable.name) return str(inp.variable.name)
if isinstance(inp, ourlang.BinaryOp): if isinstance(inp, ourlang.BinaryOp):
return f'{expression(inp.left)} {inp.operator.name} {expression(inp.right)}' return f'{expression(inp.left)} {inp.operator.function.name} {expression(inp.right)}'
if isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.FunctionCall):
args = ', '.join( args = ', '.join(
@ -85,7 +76,7 @@ def expression(inp: ourlang.Expression) -> str:
) )
if isinstance(inp.function_instance.function, ourlang.StructConstructor): if isinstance(inp.function_instance.function, ourlang.StructConstructor):
return f'{inp.function.struct_type3.name}({args})' return f'{inp.function_instance.function.struct_type5.name}({args})'
return f'{inp.function_instance.function.name}({args})' return f'{inp.function_instance.function.name}({args})'
@ -148,12 +139,17 @@ def function(mod: ourlang.Module[Any], inp: ourlang.Function) -> str:
if inp.imported: if inp.imported:
result += '@imported\n' result += '@imported\n'
assert inp.type5 is not None
fn_args = mod.build.type5_is_function(inp.type5)
assert fn_args is not None
ret_type5 = fn_args.pop()
args = ', '.join( args = ', '.join(
f'{p.name}: {type5(mod, p.type5)}' f'{arg_name}: {type5(mod, arg_type)}'
for p in inp.posonlyargs for arg_name, arg_type in zip(inp.arg_names, fn_args, strict=True)
) )
result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n' result += f'def {inp.name}({args}) -> {type5(mod, ret_type5)}:\n'
if inp.imported: if inp.imported:
result += ' pass\n' result += ' pass\n'
@ -174,12 +170,12 @@ def module(inp: ourlang.Module[Any]) -> str:
for struct in inp.struct_definitions.values(): for struct in inp.struct_definitions.values():
if result: if result:
result += '\n' result += '\n'
result += struct_definition(struct) result += struct_definition(inp, struct)
for cdef in inp.constant_defs.values(): for cdef in inp.constant_defs.values():
if result: if result:
result += '\n' result += '\n'
result += constant_definition(cdef) result += constant_definition(inp, cdef)
for func in inp.functions.values(): for func in inp.functions.values():
if isinstance(func, ourlang.StructConstructor): if isinstance(func, ourlang.StructConstructor):

View File

@ -10,14 +10,8 @@ from .build.base import BuildBase, TypeInfo
from .build.typerouter import BuildTypeRouter from .build.typerouter import BuildTypeRouter
from .stdlib import alloc as stdlib_alloc from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types from .stdlib import types as stdlib_types
from .type3.functions import FunctionArgument, TypeVariable from .type5.typeexpr import AtomicType, ConstrainedExpr, TypeApplication, TypeExpr, is_concrete
from .type3.routers import NoRouteForTypeException from .type5.unify import ActionList, ReplaceVariable, unify
from .type3.typeclasses import Type3ClassMethod
from .type3.types import (
Type3,
)
from .type5.typeexpr import TypeExpr, is_concrete
from .type5.unify import ReplaceVariable, unify
from .wasm import ( from .wasm import (
WasmTypeFloat32, WasmTypeFloat32,
WasmTypeFloat64, WasmTypeFloat64,
@ -121,7 +115,7 @@ def tuple_instantiation(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator],
exp_type_info = mod.build.type_info_map.get(element.type5.name) exp_type_info = mod.build.type_info_map.get(element.type5.name)
if exp_type_info is None: if exp_type_info is None:
exp_type_info = mod.build.type_info_map['ptr'] exp_type_info = mod.build.type_info_constructed
wgn.add_statement('nop', comment='PRE') wgn.add_statement('nop', comment='PRE')
wgn.local.get(tmp_var) wgn.local.get(tmp_var)
@ -150,11 +144,14 @@ def expression_subscript_tuple(wgn: WasmGenerator, mod: ourlang.Module[WasmGener
el_type = args[inp.index.value] el_type = args[inp.index.value]
el_type_info = mod.build.type_info_map.get(el_type.name) el_type_info = mod.build.type_info_map.get(el_type.name)
if el_type_info is None: if el_type_info is None:
el_type_info = mod.build.type_info_map['ptr'] el_type_info = mod.build.type_info_constructed
expression(wgn, mod, inp.varref) expression(wgn, mod, inp.varref)
wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}') wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}')
def expression_binary_op(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.BinaryOp) -> None:
expression_function_call(wgn, mod, _binary_op_to_function(inp))
def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.FunctionCall) -> None: def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.FunctionCall) -> None:
for arg in inp.arguments: for arg in inp.arguments:
expression(wgn, mod, arg) expression(wgn, mod, arg)
@ -162,10 +159,16 @@ def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerat
if isinstance(inp.function_instance.function, ourlang.BuiltinFunction): if isinstance(inp.function_instance.function, ourlang.BuiltinFunction):
assert _is_concrete(inp.function_instance.type5), TYPE5_ASSERTION_ERROR assert _is_concrete(inp.function_instance.type5), TYPE5_ASSERTION_ERROR
try:
method_type, method_router = mod.build.methods[inp.function_instance.function.name] method_type, method_router = mod.build.methods[inp.function_instance.function.name]
except KeyError:
method_type, method_router = mod.build.operators[inp.function_instance.function.name]
method_type = method_type.expr if isinstance(method_type, ConstrainedExpr) else method_type
instance_type = inp.function_instance.type5 instance_type = inp.function_instance.type5
actions = unify(method_type, instance_type) actions = unify(method_type, instance_type)
assert isinstance(actions, ActionList)
tv_map = {} tv_map = {}
for action in actions: for action in actions:
@ -177,8 +180,10 @@ def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerat
method_router(tv_map.keys(), tv_map.values())(wgn, None) method_router(tv_map.keys(), tv_map.values())(wgn, None)
return return
if isinstance(inp.function, ourlang.FunctionParam): if isinstance(inp.function_instance.function, ourlang.FunctionParam):
fn_args = mod.build.type5_is_function(inp.function.type5) assert _is_concrete(inp.function_instance.type5), TYPE5_ASSERTION_ERROR
fn_args = mod.build.type5_is_function(inp.function_instance.type5)
assert fn_args is not None assert fn_args is not None
params = [ params = [
@ -188,11 +193,11 @@ def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerat
result = params.pop() result = params.pop()
wgn.add_statement('local.get', '${}'.format(inp.function.name)) wgn.add_statement('local.get', '${}'.format(inp.function_instance.function.name))
wgn.call_indirect(params=params, result=result) wgn.call_indirect(params=params, result=result)
return return
wgn.call(inp.function.name) wgn.call(inp.function_instance.function.name)
def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Expression) -> None: def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Expression) -> None:
""" """
@ -255,30 +260,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
raise NotImplementedError(expression, inp.variable) raise NotImplementedError(expression, inp.variable)
if isinstance(inp, ourlang.BinaryOp): if isinstance(inp, ourlang.BinaryOp):
expression(wgn, mod, inp.left) expression_binary_op(wgn, mod, inp)
expression(wgn, mod, inp.right)
type_var_map: dict[TypeVariable, Type3] = {}
for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True):
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
if isinstance(type_var, Type3):
# Fixed type, not part of the lookup requirements
continue
if isinstance(type_var, TypeVariable):
type_var_map[type_var] = arg_expr.type3
continue
if isinstance(type_var, FunctionArgument):
# Fixed type, not part of the lookup requirements
continue
raise NotImplementedError(type_var, arg_expr.type3)
router = mod.build.type_class_instance_methods[inp.operator]
router(wgn, type_var_map)
return return
if isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.FunctionCall):
@ -325,10 +307,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl
member_type = dict(st_args)[inp.member] member_type = dict(st_args)[inp.member]
member_type_info = mod.build.type_info_map.get(member_type.name) member_type_info = mod.build.type_info_map.get(member_type.name)
if member_type_info is None: if member_type_info is None:
member_type_info = mod.build.type_info_map['ptr'] member_type_info = mod.build.type_info_constructed
offset = mod.build.type5_struct_offset( offset = _type5_struct_offset(mod.build, st_args, inp.member)
st_args, inp.member,
)
expression(wgn, mod, inp.varref) expression(wgn, mod, inp.varref)
wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(offset)) wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(offset))
@ -343,11 +323,11 @@ def statement_return(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun
# Support tail calls # Support tail calls
# https://github.com/WebAssembly/tail-call # https://github.com/WebAssembly/tail-call
# These help a lot with some functional programming techniques # These help a lot with some functional programming techniques
if isinstance(inp.value, ourlang.FunctionCall) and inp.value.function_instance.function.name is fun: if isinstance(inp.value, ourlang.FunctionCall) and inp.value.function_instance.function is fun:
for arg in inp.value.arguments: for arg in inp.value.arguments:
expression(wgn, mod, arg) expression(wgn, mod, arg)
wgn.add_statement('return_call', '${}'.format(inp.value.function.name)) wgn.add_statement('return_call', '${}'.format(inp.value.function_instance.function.name))
return return
expression(wgn, mod, inp.value) expression(wgn, mod, inp.value)
@ -464,7 +444,7 @@ def module_data(mod: ourlang.Module[WasmGenerator], inp: ourlang.ModuleData) ->
""" """
unalloc_ptr = stdlib_alloc.UNALLOC_PTR unalloc_ptr = stdlib_alloc.UNALLOC_PTR
u32_type_info = mod.build.type_info_map['u32'] u32_type_info = mod.build.type_info_map['u32']
ptr_type_info = mod.build.type_info_map['ptr'] ptr_type_info = mod.build.type_info_constructed
allocated_data = b'' allocated_data = b''
@ -580,7 +560,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGen
for memname, mtyp5 in st_args: for memname, mtyp5 in st_args:
mtyp5_info = mod.build.type_info_map.get(mtyp5.name) mtyp5_info = mod.build.type_info_map.get(mtyp5.name)
if mtyp5_info is None: if mtyp5_info is None:
mtyp5_info = mod.build.type_info_map['ptr'] mtyp5_info = mod.build.type_info_constructed
wgn.local.get(tmp_var) wgn.local.get(tmp_var)
wgn.add_statement('local.get', f'${memname}') wgn.add_statement('local.get', f'${memname}')
@ -591,8 +571,40 @@ def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGen
# Return the allocated address # Return the allocated address
wgn.local.get(tmp_var) wgn.local.get(tmp_var)
def _is_concrete(type5: TypeExpr | None) -> TypeGuard[TypeExpr]: def _is_concrete(type5: TypeExpr | ConstrainedExpr | None) -> TypeGuard[TypeExpr]:
if type5 is None: if type5 is None:
return False return False
if isinstance(type5, ConstrainedExpr):
type5 = type5.expr
return is_concrete(type5) return is_concrete(type5)
def _type5_struct_offset(
build: BuildBase[Any],
fields: tuple[tuple[str, AtomicType | TypeApplication], ...],
needle: str,
) -> int:
"""
Calculates the amount of bytes that should be skipped in memory befor reaching the struct's property with the given name.
"""
result = 0
for memnam, memtyp in fields:
if needle == memnam:
return result
result += build.type5_alloc_size_member(memtyp)
raise RuntimeError('Member not found')
def _binary_op_to_function(inp: ourlang.BinaryOp) -> ourlang.FunctionCall:
"""
For compilation purposes, a binary operator is just a function call.
It's only syntactic sugar - e.g. `1 + 2` vs `+(1, 2)`
"""
assert inp.sourceref is not None # TODO: sourceref required
call = ourlang.FunctionCall(inp.operator, inp.sourceref)
call.arguments = [inp.left, inp.right]
return call

View File

@ -6,8 +6,6 @@ from __future__ import annotations
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.types import Type3, TypeApplication_Struct
from .type5 import record as type5record from .type5 import record as type5record
from .type5 import typeexpr as type5typeexpr from .type5 import typeexpr as type5typeexpr
@ -34,15 +32,13 @@ class Expression:
""" """
An expression within a statement An expression within a statement
""" """
__slots__ = ('type3', 'type5', 'sourceref', ) __slots__ = ('type5', 'sourceref', )
sourceref: SourceRef sourceref: SourceRef
type3: Type3 | None
type5: type5typeexpr.TypeExpr | None type5: type5typeexpr.TypeExpr | None
def __init__(self, *, sourceref: SourceRef) -> None: def __init__(self, *, sourceref: SourceRef) -> None:
self.sourceref = sourceref self.sourceref = sourceref
self.type3 = None
self.type5 = None self.type5 = None
class Constant(Expression): class Constant(Expression):
@ -122,22 +118,19 @@ class ConstantStruct(ConstantMemoryStored):
""" """
A Struct constant value expression within a statement A Struct constant value expression within a statement
""" """
__slots__ = ('struct_type3', 'struct_type5', 'value', ) __slots__ = ('struct_type5', 'value', )
struct_type3: Type3
struct_type5: type5record.Record struct_type5: type5record.Record
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']] value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']]
def __init__( def __init__(
self, self,
struct_type3: Type3,
struct_type5: type5record.Record, struct_type5: type5record.Record,
value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']], value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']],
data_block: 'ModuleDataBlock', data_block: 'ModuleDataBlock',
sourceref: SourceRef sourceref: SourceRef
) -> None: ) -> None:
super().__init__(data_block, sourceref=sourceref) super().__init__(data_block, sourceref=sourceref)
self.struct_type3 = struct_type3
self.struct_type5 = struct_type5 self.struct_type5 = struct_type5
self.value = value self.value = value
@ -145,7 +138,7 @@ class ConstantStruct(ConstantMemoryStored):
# Do not repr the whole ModuleDataBlock # Do not repr the whole ModuleDataBlock
# As this has a reference back to this constant for its data # As this has a reference back to this constant for its data
# which it needs to compile the data into the program # which it needs to compile the data into the program
return f'ConstantStruct({self.struct_type3!r}, {self.value!r}, @{self.data_block.address!r})' return f'ConstantStruct({self.struct_type5!r}, {self.value!r}, @{self.data_block.address!r})'
class VariableReference(Expression): class VariableReference(Expression):
""" """
@ -253,17 +246,15 @@ class AccessStructMember(Expression):
""" """
Access a struct member for reading of writing Access a struct member for reading of writing
""" """
__slots__ = ('varref', 'struct_type3', 'member', ) __slots__ = ('varref', 'member', )
varref: VariableReference varref: VariableReference
struct_type3: Type3 # FIXME: Remove this (already at varref.type5)
member: str member: str
def __init__(self, varref: VariableReference, struct_type3: Type3, member: str, sourceref: SourceRef) -> None: def __init__(self, varref: VariableReference, member: str, sourceref: SourceRef) -> None:
super().__init__(sourceref=sourceref) super().__init__(sourceref=sourceref)
self.varref = varref self.varref = varref
self.struct_type3 = struct_type3
self.member = member self.member = member
class Statement: class Statement:
@ -319,68 +310,60 @@ class FunctionParam:
""" """
A parameter for a Function A parameter for a Function
""" """
__slots__ = ('name', 'type3', 'type5', ) __slots__ = ('name', 'type5', )
name: str name: str
type3: Type3
type5: type5typeexpr.TypeExpr type5: type5typeexpr.TypeExpr
def __init__(self, name: str, type3: Type3, type5: type5typeexpr.TypeExpr) -> None: def __init__(self, name: str, type5: type5typeexpr.TypeExpr) -> None:
assert type5typeexpr.is_concrete(type5) assert type5typeexpr.is_concrete(type5)
self.name = name self.name = name
self.type3 = type3
self.type5 = type5 self.type5 = type5
def __repr__(self) -> str: def __repr__(self) -> str:
return f'FunctionParam({self.name!r}, {self.type3!r})' return f'FunctionParam({self.name!r}, {self.type5!r})'
class Function: class Function:
""" """
A function processes input and produces output A function processes input and produces output
""" """
__slots__ = ('name', 'sourceref', 'exported', 'imported', 'statements', 'signature', 'returns_type3', 'type5', 'posonlyargs', 'arg_names', ) __slots__ = ('name', 'sourceref', 'exported', 'imported', 'statements', 'type5', 'posonlyargs', 'arg_names', )
name: str name: str
sourceref: SourceRef sourceref: SourceRef
exported: bool exported: bool
imported: Optional[str] imported: Optional[str]
statements: List[Statement] statements: List[Statement]
signature: FunctionSignature # TODO: Remove me type5: type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr | None
returns_type3: Type3 # TODO: Remove me
type5: type5typeexpr.TypeExpr | None
posonlyargs: List[FunctionParam] # TODO: Replace me with arg names posonlyargs: List[FunctionParam] # TODO: Replace me with arg names
arg_names: list[str] arg_names: list[str]
def __init__(self, name: str, sourceref: SourceRef, returns_type3: Type3) -> None: def __init__(self, name: str, sourceref: SourceRef) -> None:
self.name = name self.name = name
self.sourceref = sourceref self.sourceref = sourceref
self.exported = False self.exported = False
self.imported = None self.imported = None
self.statements = [] self.statements = []
self.signature = FunctionSignature(TypeVariableContext(), [])
self.returns_type3 = returns_type3
self.type5 = None self.type5 = None
self.posonlyargs = [] self.posonlyargs = []
self.arg_names = [] self.arg_names = []
class BuiltinFunction(Function): class BuiltinFunction(Function):
def __init__(self, name: str, type5: type5typeexpr.TypeExpr) -> None: def __init__(self, name: str, type5: type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr) -> None:
super().__init__(name, SourceRef("/", 0, 0), None) super().__init__(name, SourceRef("/", 0, 0))
self.type5 = type5 self.type5 = type5
class StructDefinition: class StructDefinition:
""" """
The definition for a struct The definition for a struct
""" """
__slots__ = ('struct_type3', 'struct_type5', 'sourceref', ) __slots__ = ('struct_type5', 'sourceref', )
struct_type3: Type3
struct_type5: type5record.Record struct_type5: type5record.Record
sourceref: SourceRef sourceref: SourceRef
def __init__(self, struct_type3: Type3, struct_type5: type5record.Record, sourceref: SourceRef) -> None: def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
self.struct_type3 = struct_type3
self.struct_type5 = struct_type5 self.struct_type5 = struct_type5
self.sourceref = sourceref self.sourceref = sourceref
@ -391,44 +374,32 @@ class StructConstructor(Function):
A function will generated to instantiate a struct. The arguments A function will generated to instantiate a struct. The arguments
will be the defaults will be the defaults
""" """
__slots__ = ('struct_type3', 'struct_type5', ) __slots__ = ('struct_type5', )
struct_type3: Type3
struct_type5: type5record.Record struct_type5: type5record.Record
def __init__(self, struct_type3: Type3, struct_type5: type5record.Record, sourceref: SourceRef) -> None: def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None:
super().__init__(f'@{struct_type3.name}@__init___@', sourceref, struct_type3) super().__init__(f'@{struct_type5.name}@__init___@', sourceref)
assert isinstance(struct_type3.application, TypeApplication_Struct)
mem_typ5_map = dict(struct_type5.fields)
for mem, typ in struct_type3.application.arguments:
self.arg_names.append(mem)
self.posonlyargs.append(FunctionParam(mem, typ, mem_typ5_map[mem]))
self.signature.args.append(typ)
self.signature.args.append(struct_type3)
self.struct_type3 = struct_type3
self.struct_type5 = struct_type5 self.struct_type5 = struct_type5
for mem, typ in struct_type5.fields:
self.arg_names.append(mem)
self.posonlyargs.append(FunctionParam(mem, typ))
class ModuleConstantDef: class ModuleConstantDef:
""" """
A constant definition within a module A constant definition within a module
""" """
__slots__ = ('name', 'sourceref', 'type3', 'type5', 'constant', ) __slots__ = ('name', 'sourceref', 'type5', 'constant', )
name: str name: str
sourceref: SourceRef sourceref: SourceRef
type3: Type3
type5: type5typeexpr.TypeExpr type5: type5typeexpr.TypeExpr
constant: Constant constant: Constant
def __init__(self, name: str, sourceref: SourceRef, type3: Type3, type5: type5typeexpr.TypeExpr, constant: Constant) -> None: def __init__(self, name: str, sourceref: SourceRef, type5: type5typeexpr.TypeExpr, constant: Constant) -> None:
self.name = name self.name = name
self.sourceref = sourceref self.sourceref = sourceref
self.type3 = type3
self.type5 = type5 self.type5 = type5
self.constant = constant self.constant = constant
@ -468,13 +439,12 @@ class Module[G]:
build: BuildBase[G] build: BuildBase[G]
filename: str filename: str
data: ModuleData data: ModuleData
types: dict[str, Type3] types: dict[str, type5typeexpr.TypeExpr]
type5s: dict[str, type5typeexpr.TypeExpr]
struct_definitions: Dict[str, StructDefinition] struct_definitions: Dict[str, StructDefinition]
constant_defs: Dict[str, ModuleConstantDef] constant_defs: Dict[str, ModuleConstantDef]
functions: Dict[str, Function] functions: Dict[str, Function]
methods: Dict[str, type5typeexpr.TypeExpr] methods: Dict[str, type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr]
operators: Dict[str, type5typeexpr.TypeExpr] operators: Dict[str, type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr]
functions_table: dict[Function, int] functions_table: dict[Function, int]
def __init__(self, build: BuildBase[G], filename: str) -> None: def __init__(self, build: BuildBase[G], filename: str) -> None:
@ -483,7 +453,6 @@ class Module[G]:
self.data = ModuleData() self.data = ModuleData()
self.types = {} self.types = {}
self.type5s = {}
self.struct_definitions = {} self.struct_definitions = {}
self.constant_defs = {} self.constant_defs = {}
self.functions = {} self.functions = {}

View File

@ -35,8 +35,6 @@ from .ourlang import (
TupleInstantiation, TupleInstantiation,
VariableReference, VariableReference,
) )
from .type3.typeclasses import Type3ClassMethod
from .type3.types import IntType3, Type3
from .type5 import typeexpr as type5typeexpr from .type5 import typeexpr as type5typeexpr
from .wasmgenerator import Generator from .wasmgenerator import Generator
@ -103,9 +101,8 @@ class OurVisitor[G]:
module = Module(self.build, "-") module = Module(self.build, "-")
module.methods.update({k: v[0] for k, v in self.build.methods.items()}) module.methods.update({k: v[0] for k, v in self.build.methods.items()})
module.operators.update(self.build.operators) module.operators.update({k: v[0] for k, v in self.build.operators.items()})
module.types.update(self.build.types) module.types.update(self.build.types)
module.type5s.update(self.build.type5s)
_not_implemented(not node.type_ignores, 'Module.type_ignores') _not_implemented(not node.type_ignores, 'Module.type_ignores')
@ -123,17 +120,16 @@ class OurVisitor[G]:
module.constant_defs[res.name] = res module.constant_defs[res.name] = res
if isinstance(res, StructDefinition): if isinstance(res, StructDefinition):
if res.struct_type3.name in module.types: if res.struct_type5.name in module.types:
raise StaticError( raise StaticError(
f'{res.struct_type3.name} already defined as type' f'{res.struct_type5.name} already defined as type'
) )
module.types[res.struct_type3.name] = res.struct_type3 module.types[res.struct_type5.name] = res.struct_type5
module.type5s[res.struct_type5.name] = res.struct_type5 module.functions[res.struct_type5.name] = StructConstructor(res.struct_type5, res.sourceref)
module.functions[res.struct_type3.name] = StructConstructor(res.struct_type3, res.struct_type5, res.sourceref) module.functions[res.struct_type5.name].type5 = module.build.type5_make_function([x[1] for x in res.struct_type5.fields] + [res.struct_type5])
module.functions[res.struct_type3.name].type5 = module.build.type5_make_function([x[1] for x in res.struct_type5.fields] + [res.struct_type5])
# 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_type5.name] = res
if isinstance(res, Function): if isinstance(res, Function):
if res.name in module.functions: if res.name in module.functions:
@ -163,7 +159,7 @@ 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, srf(module, node), self.build.none_) function = Function(node.name, srf(module, node))
_not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs') _not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs')
@ -173,7 +169,6 @@ class OurVisitor[G]:
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_type5 = self.visit_type5(module, arg.annotation) arg_type5 = self.visit_type5(module, arg.annotation)
arg_type5_list.append(arg_type5) arg_type5_list.append(arg_type5)
@ -182,10 +177,8 @@ class OurVisitor[G]:
# This would also require FunctionParam to accept a placeholder # This would also require FunctionParam to accept a placeholder
function.arg_names.append(arg.arg) function.arg_names.append(arg.arg)
function.signature.args.append(arg_type)
function.posonlyargs.append(FunctionParam( function.posonlyargs.append(FunctionParam(
arg.arg, arg.arg,
arg_type,
arg_type5, arg_type5,
)) ))
@ -229,12 +222,8 @@ class OurVisitor[G]:
if node.returns is None: # Note: `-> None` would be a ast.Constant if node.returns is None: # Note: `-> None` would be a ast.Constant
_raise_static_error(node, 'Must give a return type') _raise_static_error(node, 'Must give a return type')
return_type = self.visit_type(module, node.returns)
arg_type5_list.append(self.visit_type5(module, node.returns)) arg_type5_list.append(self.visit_type5(module, node.returns))
function.signature.args.append(return_type)
function.returns_type3 = return_type
function.type5 = module.build.type5_make_function(arg_type5_list) function.type5 = module.build.type5_make_function(arg_type5_list)
_not_implemented(not node.type_comment, 'FunctionDef.type_comment') _not_implemented(not node.type_comment, 'FunctionDef.type_comment')
@ -247,8 +236,7 @@ class OurVisitor[G]:
_not_implemented(not node.keywords, 'ClassDef.keywords') _not_implemented(not node.keywords, 'ClassDef.keywords')
_not_implemented(not node.decorator_list, 'ClassDef.decorator_list') _not_implemented(not node.decorator_list, 'ClassDef.decorator_list')
members: Dict[str, Type3] = {} members: Dict[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication] = {}
members5: Dict[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication] = {}
for stmt in node.body: for stmt in node.body:
if not isinstance(stmt, ast.AnnAssign): if not isinstance(stmt, ast.AnnAssign):
@ -266,15 +254,12 @@ class OurVisitor[G]:
if stmt.target.id in members: if stmt.target.id in members:
_raise_static_error(stmt, 'Struct members must have unique names') _raise_static_error(stmt, 'Struct members must have unique names')
members[stmt.target.id] = self.visit_type(module, stmt.annotation)
field_type5 = self.visit_type5(module, stmt.annotation) field_type5 = self.visit_type5(module, stmt.annotation)
assert isinstance(field_type5, (type5typeexpr.AtomicType, type5typeexpr.TypeApplication, )) assert isinstance(field_type5, (type5typeexpr.AtomicType, type5typeexpr.TypeApplication, ))
members5[stmt.target.id] = field_type5 members[stmt.target.id] = field_type5
return StructDefinition( return StructDefinition(
module.build.struct(node.name, tuple(members.items())), module.build.type5_make_struct(node.name, tuple(members.items())),
module.build.type5_make_struct(node.name, tuple(members5.items())),
srf(module, node), srf(module, node),
) )
@ -290,7 +275,6 @@ class OurVisitor[G]:
return ModuleConstantDef( return ModuleConstantDef(
node.target.id, node.target.id,
srf(module, node), srf(module, node),
self.visit_type(module, node.annotation),
self.visit_type5(module, node.annotation), self.visit_type5(module, node.annotation),
value_data, value_data,
) )
@ -304,7 +288,6 @@ class OurVisitor[G]:
return ModuleConstantDef( return ModuleConstantDef(
node.target.id, node.target.id,
srf(module, node), srf(module, node),
self.visit_type(module, node.annotation),
self.visit_type5(module, node.annotation), self.visit_type5(module, node.annotation),
value_data, value_data,
) )
@ -318,7 +301,6 @@ class OurVisitor[G]:
return ModuleConstantDef( return ModuleConstantDef(
node.target.id, node.target.id,
srf(module, node), srf(module, node),
self.visit_type(module, node.annotation),
self.visit_type5(module, node.annotation), self.visit_type5(module, node.annotation),
value_data, value_data,
) )
@ -386,7 +368,7 @@ class OurVisitor[G]:
def visit_Module_FunctionDef_expr(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.expr) -> Expression: def visit_Module_FunctionDef_expr(self, module: Module[G], function: Function, our_locals: OurLocals, node: ast.expr) -> Expression:
if isinstance(node, ast.BinOp): if isinstance(node, ast.BinOp):
operator: Union[str, Type3ClassMethod] operator: str
if isinstance(node.op, ast.Add): if isinstance(node.op, ast.Add):
operator = '+' operator = '+'
@ -417,7 +399,7 @@ class OurVisitor[G]:
raise NotImplementedError(f'Operator {operator}') raise NotImplementedError(f'Operator {operator}')
return BinaryOp( return BinaryOp(
FunctionInstance(module.operators[operator], srf(module, node)), FunctionInstance(BuiltinFunction(operator, module.operators[operator]), srf(module, node)),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.right),
srf(module, node), srf(module, node),
@ -446,7 +428,7 @@ class OurVisitor[G]:
raise NotImplementedError(f'Operator {operator}') raise NotImplementedError(f'Operator {operator}')
return BinaryOp( return BinaryOp(
FunctionInstance(module.operators[operator], srf(module, node)), FunctionInstance(BuiltinFunction(operator, module.operators[operator]), srf(module, node)),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.left),
self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]), self.visit_Module_FunctionDef_expr(module, function, our_locals, node.comparators[0]),
srf(module, node), srf(module, node),
@ -511,7 +493,7 @@ 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, FunctionParam]
if node.func.id in module.methods: if node.func.id in module.methods:
func = BuiltinFunction(node.func.id, module.methods[node.func.id]) func = BuiltinFunction(node.func.id, module.methods[node.func.id])
@ -543,7 +525,6 @@ class OurVisitor[G]:
return AccessStructMember( return AccessStructMember(
varref, varref,
varref.variable.type3,
node.attr, node.attr,
srf(module, node), srf(module, node),
) )
@ -617,7 +598,7 @@ class OurVisitor[G]:
data_block = ModuleDataBlock(struct_data) data_block = ModuleDataBlock(struct_data)
module.data.blocks.append(data_block) module.data.blocks.append(data_block)
return ConstantStruct(struct_def.struct_type3, struct_def.struct_type5, struct_data, data_block, srf(module, node)) return ConstantStruct(struct_def.struct_type5, struct_data, data_block, srf(module, node))
_not_implemented(node.kind is None, 'Constant.kind') _not_implemented(node.kind is None, 'Constant.kind')
@ -634,70 +615,6 @@ class OurVisitor[G]:
raise NotImplementedError(f'{node.value} as constant') raise NotImplementedError(f'{node.value} as constant')
def visit_type(self, module: Module[G], node: ast.expr) -> Type3:
if isinstance(node, ast.Constant):
if node.value is None:
return module.types['None']
_raise_static_error(node, f'Unrecognized type {node.value!r}')
if isinstance(node, ast.Name):
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
if node.id in module.types:
return module.types[node.id]
_raise_static_error(node, f'Unrecognized type {node.id}')
if isinstance(node, ast.Subscript):
if isinstance(node.value, ast.Name) and node.value.id == 'Callable':
func_arg_types: list[ast.expr]
if isinstance(node.slice, ast.Name):
func_arg_types = [node.slice]
elif isinstance(node.slice, ast.Tuple):
func_arg_types = node.slice.elts
else:
_raise_static_error(node, 'Must subscript using a list of types')
# Function type
return module.build.function(*[
self.visit_type(module, e)
for e in func_arg_types
])
if isinstance(node.slice, ast.Slice):
_raise_static_error(node, 'Must subscript using an index')
if not isinstance(node.slice, ast.Constant):
_raise_static_error(node, 'Must subscript using a constant index')
if node.slice.value is Ellipsis:
return module.build.dynamic_array(
self.visit_type(module, node.value),
)
if not isinstance(node.slice.value, int):
_raise_static_error(node, 'Must subscript using a constant integer index')
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
return module.build.static_array(
self.visit_type(module, node.value),
IntType3(node.slice.value),
)
if isinstance(node, ast.Tuple):
if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context')
return module.build.tuple_(
*(self.visit_type(module, elt) for elt in node.elts)
)
raise NotImplementedError(f'{node} as type')
def visit_type5(self, module: Module[G], node: ast.expr) -> type5typeexpr.TypeExpr: def visit_type5(self, module: Module[G], node: ast.expr) -> type5typeexpr.TypeExpr:
if isinstance(node, ast.Constant): if isinstance(node, ast.Constant):
if node.value is None: if node.value is None:
@ -709,8 +626,8 @@ class OurVisitor[G]:
if not isinstance(node.ctx, ast.Load): if not isinstance(node.ctx, ast.Load):
_raise_static_error(node, 'Must be load context') _raise_static_error(node, 'Must be load context')
if node.id in module.type5s: if node.id in module.types:
return module.type5s[node.id] return module.types[node.id]
_raise_static_error(node, f'Unrecognized type {node.id}') _raise_static_error(node, f'Unrecognized type {node.id}')

View File

@ -1,704 +0,0 @@
"""
This module contains possible constraints generated based on the AST
These need to be resolved before the program can be compiled.
"""
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from .. import ourlang
from ..build.base import BuildBase
from ..wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
)
from .functions import FunctionArgument, TypeVariable
from .placeholders import PlaceholderForType, Type3OrPlaceholder
from .routers import NoRouteForTypeException, TypeApplicationRouter
from .typeclasses import Type3Class
from .types import (
IntType3,
Type3,
TypeApplication_Nullary,
TypeApplication_Struct,
TypeApplication_Type,
TypeApplication_TypeInt,
TypeApplication_TypeStar,
TypeConstructor_Base,
TypeConstructor_Struct,
)
class Error:
"""
An error returned by the check functions for a contraint
This means the programmer has to make some kind of chance to the
typing of their program before the compiler can do its thing.
"""
def __init__(self, msg: str, *, comment: Optional[str] = None) -> None:
self.msg = msg
self.comment = comment
def __repr__(self) -> str:
return f'Error({repr(self.msg)}, comment={repr(self.comment)})'
class RequireTypeSubstitutes:
"""
Returned by the check function for a contraint if they do not have all
their types substituted yet.
Hopefully, another constraint will give the right information about the
typing of the program, so this constraint can be updated.
"""
SubstitutionMap = Dict[PlaceholderForType, Type3]
NewConstraintList = List['ConstraintBase']
CheckResult = Union[None, SubstitutionMap, Error, NewConstraintList, RequireTypeSubstitutes]
HumanReadableRet = Tuple[str, Dict[str, Union[None, int, str, ourlang.Expression, Type3, PlaceholderForType]]]
class Context:
"""
Context for constraints
"""
__slots__ = ('build', )
build: BuildBase[Any]
def __init__(self, build: BuildBase[Any]) -> None:
self.build = build
class ConstraintBase:
"""
Base class for constraints
"""
__slots__ = ('context', 'comment', )
context: Context
"""
Additional information regarding the type environment
"""
comment: Optional[str]
"""
A comment to help the programmer with debugging the types in their program
"""
def __init__(self, context: Context, comment: Optional[str] = None) -> None:
self.context = context
self.comment = comment
def check(self) -> CheckResult:
"""
Checks if the constraint hold
This function can return an error, if the constraint does not hold,
which indicates an error in the typing of the input program.
This function can return RequireTypeSubstitutes(), if we cannot deduce
all the types yet.
This function can return a SubstitutionMap, if during the evaluation
of the contraint we discovered new types. In this case, the constraint
is expected to hold.
This function can return None, if the constraint holds, but no new
information was deduced from evaluating this constraint.
"""
raise NotImplementedError(self.__class__, self.check)
def human_readable(self) -> HumanReadableRet:
"""
Returns a more human readable form of this constraint
"""
return repr(self), {}
class SameTypeConstraint(ConstraintBase):
"""
Verifies that a number of types all are the same type
"""
__slots__ = ('type_list', )
type_list: List[Type3OrPlaceholder]
def __init__(self, context: Context, *type_list: Type3OrPlaceholder, comment: Optional[str] = None) -> None:
super().__init__(context=context, comment=comment)
assert len(type_list) > 1
self.type_list = [*type_list]
def check(self) -> CheckResult:
known_types: List[Type3] = []
phft_list = []
for typ in self.type_list:
if isinstance(typ, Type3):
known_types.append(typ)
continue
if isinstance(typ, PlaceholderForType):
if typ.resolve_as is not None:
known_types.append(typ.resolve_as)
else:
phft_list.append(typ)
continue
raise NotImplementedError(typ)
if not known_types:
return RequireTypeSubstitutes()
first_type = known_types[0]
for ktyp in known_types[1:]:
if ktyp != first_type:
return Error(f'{ktyp:s} must be {first_type:s} instead', comment=self.comment)
if not phft_list:
return None
for phft in phft_list:
phft.resolve_as = first_type
return {
typ: first_type
for typ in phft_list
}
def human_readable(self) -> HumanReadableRet:
return (
' == '.join('{t' + str(idx) + '}' for idx in range(len(self.type_list))),
{
't' + str(idx): typ
for idx, typ in enumerate(self.type_list)
},
)
def __repr__(self) -> str:
args = ', '.join(repr(x) for x in self.type_list)
return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
class SameTypeArgumentConstraint(ConstraintBase):
__slots__ = ('tc_var', 'arg_var', )
tc_var: PlaceholderForType
arg_var: PlaceholderForType
def __init__(self, context: Context, tc_var: PlaceholderForType, arg_var: PlaceholderForType, *, comment: str) -> None:
super().__init__(context=context, comment=comment)
self.tc_var = tc_var
self.arg_var = arg_var
def check(self) -> CheckResult:
if self.tc_var.resolve_as is None:
return RequireTypeSubstitutes()
tc_typ = self.tc_var.resolve_as
arg_typ = self.arg_var.resolve_as
if isinstance(tc_typ.application, TypeApplication_Nullary):
return Error(f'{tc_typ:s} must be a constructed type instead')
if isinstance(tc_typ.application, TypeApplication_TypeStar):
# Sure, it's a constructed type. But it's like a struct,
# though without the way to implement type classes
# Presumably, doing a naked `foo :: t a -> a`
# doesn't work since you don't have any info on t
# So we can let the MustImplementTypeClassConstraint handle it.
return None
if isinstance(tc_typ.application, TypeApplication_Type):
return [SameTypeConstraint(
self.context,
tc_typ.application.arguments[0],
self.arg_var,
comment=self.comment,
)]
# FIXME: This feels sketchy. Shouldn't the type variable
# have the exact same number as arguments?
if isinstance(tc_typ.application, TypeApplication_TypeInt):
return [SameTypeConstraint(
self.context,
tc_typ.application.arguments[0],
self.arg_var,
comment=self.comment,
)]
raise NotImplementedError(tc_typ, arg_typ)
def human_readable(self) -> HumanReadableRet:
return (
'{tc_var}` == {arg_var}',
{
'tc_var': self.tc_var if self.tc_var.resolve_as is None else self.tc_var,
'arg_var': self.arg_var if self.arg_var.resolve_as is None else self.arg_var,
},
)
class SameFunctionArgumentConstraint(ConstraintBase):
__slots__ = ('type3', 'func_arg', 'type_var_map', )
type3: PlaceholderForType
func_arg: FunctionArgument
type_var_map: dict[TypeVariable, PlaceholderForType]
def __init__(self, context: Context, type3: PlaceholderForType, func_arg: FunctionArgument, type_var_map: dict[TypeVariable, PlaceholderForType], *, comment: str) -> None:
super().__init__(context=context, comment=comment)
self.type3 = type3
self.func_arg = func_arg
self.type_var_map = type_var_map
def check(self) -> CheckResult:
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
typ = self.type3.resolve_as
if isinstance(typ.application, TypeApplication_Nullary):
return Error(f'{typ:s} must be a function instead')
if not isinstance(typ.application, TypeApplication_TypeStar):
return Error(f'{typ:s} must be a function instead')
type_var_map = {
x: y.resolve_as
for x, y in self.type_var_map.items()
if y.resolve_as is not None
}
exp_type_arg_list = [
tv if isinstance(tv, Type3) else type_var_map[tv]
for tv in self.func_arg.args
if isinstance(tv, Type3) or tv in type_var_map
]
if len(exp_type_arg_list) != len(self.func_arg.args):
return RequireTypeSubstitutes()
return [
SameTypeConstraint(
self.context,
typ,
self.context.build.function(*exp_type_arg_list),
comment=self.comment,
)
]
def human_readable(self) -> HumanReadableRet:
return (
'{type3} == {func_arg}',
{
'type3': self.type3,
'func_arg': self.func_arg.name,
},
)
class TupleMatchConstraint(ConstraintBase):
__slots__ = ('exp_type', 'args', 'generate_router', )
exp_type: Type3OrPlaceholder
args: list[Type3OrPlaceholder]
generate_router: TypeApplicationRouter['TupleMatchConstraint', CheckResult]
def __init__(self, context: Context, exp_type: Type3OrPlaceholder, args: Iterable[Type3OrPlaceholder], comment: str):
super().__init__(context=context, comment=comment)
self.exp_type = exp_type
self.args = list(args)
self.generate_router = TypeApplicationRouter()
self.generate_router.add(context.build.dynamic_array, self.__class__._generate_dynamic_array)
self.generate_router.add(context.build.static_array, self.__class__._generate_static_array)
self.generate_router.add(context.build.tuple_, self.__class__._generate_tuple)
def _generate_dynamic_array(self, sa_args: tuple[Type3]) -> CheckResult:
sa_type, = sa_args
return [
SameTypeConstraint(self.context, arg, sa_type)
for arg in self.args
]
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
sa_type, sa_len = sa_args
if sa_len.value != len(self.args):
return Error('Mismatch between applied types argument count', comment=self.comment)
return [
SameTypeConstraint(self.context, arg, sa_type)
for arg in self.args
]
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult:
if len(tp_args) != len(self.args):
return Error('Mismatch between applied types argument count', comment=self.comment)
return [
SameTypeConstraint(self.context, arg, oth_arg)
for arg, oth_arg in zip(self.args, tp_args, strict=True)
]
def check(self) -> CheckResult:
exp_type = self.exp_type
if isinstance(exp_type, PlaceholderForType):
if exp_type.resolve_as is None:
return RequireTypeSubstitutes()
exp_type = exp_type.resolve_as
try:
return self.generate_router(self, exp_type)
except NoRouteForTypeException:
raise NotImplementedError(exp_type)
class MustImplementTypeClassConstraint(ConstraintBase):
"""
A type must implement a given type class
"""
__slots__ = ('type_class3', 'types', )
type_class3: Type3Class
types: list[Type3OrPlaceholder]
def __init__(self, context: Context, type_class3: Type3Class, typ_list: list[Type3OrPlaceholder], comment: Optional[str] = None) -> None:
super().__init__(context=context, comment=comment)
self.type_class3 = type_class3
self.types = typ_list
def check(self) -> CheckResult:
typ_list: list[Type3 | TypeConstructor_Base[Any] | TypeConstructor_Struct] = []
for typ in self.types:
if isinstance(typ, PlaceholderForType) and typ.resolve_as is not None:
typ = typ.resolve_as
if isinstance(typ, PlaceholderForType):
return RequireTypeSubstitutes()
if isinstance(typ.application, (TypeApplication_Nullary, TypeApplication_Struct, )):
typ_list.append(typ)
continue
if isinstance(typ.application, (TypeApplication_Type, TypeApplication_TypeInt, TypeApplication_TypeStar)):
typ_list.append(typ.application.constructor)
continue
raise NotImplementedError(typ, typ.application)
assert len(typ_list) == len(self.types)
key = (self.type_class3, tuple(typ_list), )
if key in self.context.build.type_class_instances:
return None
typ_cls_name = self.type_class3 if isinstance(self.type_class3, str) else self.type_class3.name
typ_name_list = ' '.join(x.name for x in typ_list)
return Error(f'Missing type class instantation: {typ_cls_name} {typ_name_list}')
def human_readable(self) -> HumanReadableRet:
keys = {
f'type{idx}': typ
for idx, typ in enumerate(self.types)
}
return (
'Exists instance {type_class3} ' + ' '.join(f'{{{x}}}' for x in keys),
{
'type_class3': str(self.type_class3),
**keys,
},
)
def __repr__(self) -> str:
return f'MustImplementTypeClassConstraint({repr(self.type_class3)}, {repr(self.types)}, comment={repr(self.comment)})'
class LiteralFitsConstraint(ConstraintBase):
"""
A literal value fits a given type
"""
__slots__ = ('type3', 'literal', 'generate_router', )
type3: Type3OrPlaceholder
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct]
generate_router: TypeApplicationRouter['LiteralFitsConstraint', CheckResult]
def __init__(
self,
context: Context,
type3: Type3OrPlaceholder,
literal: Union[ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct],
comment: Optional[str] = None,
) -> None:
super().__init__(context=context, comment=comment)
self.type3 = type3
self.literal = literal
self.generate_router = TypeApplicationRouter['LiteralFitsConstraint', CheckResult]()
self.generate_router.add(context.build.dynamic_array, self.__class__._generate_dynamic_array)
self.generate_router.add(context.build.static_array, self.__class__._generate_static_array)
self.generate_router.add(context.build.struct, self.__class__._generate_struct)
self.generate_router.add(context.build.tuple_, self.__class__._generate_tuple)
def _generate_dynamic_array(self, da_args: tuple[Type3]) -> CheckResult:
da_type, = da_args
if da_type.name == 'u8':
if not isinstance(self.literal, ourlang.ConstantBytes):
return Error('Must be bytes', comment=self.comment)
return None
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(self.context, da_type, y)
for y in self.literal.value
)
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(self.context, da_type, PlaceholderForType([y]))
for y in self.literal.value
)
return res
def _generate_static_array(self, sa_args: tuple[Type3, IntType3]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
sa_type, sa_len = sa_args
if sa_len.value != len(self.literal.value):
return Error('Member count mismatch', comment=self.comment)
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(self.context, sa_type, y)
for y in self.literal.value
)
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(self.context, sa_type, PlaceholderForType([y]))
for y in self.literal.value
)
return res
def _generate_struct(self, st_args: tuple[tuple[str, Type3], ...]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantStruct):
return Error('Must be struct')
if len(st_args) != len(self.literal.value):
return Error('Struct element count mismatch')
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(self.context, x, y)
for (_, x), y in zip(st_args, self.literal.value, strict=True)
)
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(self.context, x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_type3.name}.{x_n}')
for (x_n, x_t, ), y in zip(st_args, self.literal.value, strict=True)
)
res.append(SameTypeConstraint(
self.context,
self.literal.struct_type3,
self.type3,
comment='Struct types must match',
))
return res
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult:
if not isinstance(self.literal, ourlang.ConstantTuple):
return Error('Must be tuple', comment=self.comment)
if len(tp_args) != len(self.literal.value):
return Error('Tuple element count mismatch', comment=self.comment)
res: list[ConstraintBase] = []
res.extend(
LiteralFitsConstraint(self.context, x, y)
for x, y in zip(tp_args, self.literal.value, strict=True)
)
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend(
SameTypeConstraint(self.context, x, PlaceholderForType([y]))
for x, y in zip(tp_args, self.literal.value, strict=True)
)
return res
def check(self) -> CheckResult:
if isinstance(self.type3, PlaceholderForType):
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
self.type3 = self.type3.resolve_as
type_info = self.context.build.type_info_map.get(self.type3.name)
if type_info is not None and (type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64):
assert type_info.signed is not None
if isinstance(self.literal.value, int):
try:
self.literal.value.to_bytes(type_info.alloc_size, 'big', signed=type_info.signed)
except OverflowError:
return Error(f'Must fit in {type_info.alloc_size} byte(s)', comment=self.comment) # FIXME: Add line information
return None
return Error('Must be integer', comment=self.comment) # FIXME: Add line information
if type_info is not None and (type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64):
if isinstance(self.literal.value, float):
# FIXME: Bit check
return None
return Error('Must be real', comment=self.comment) # FIXME: Add line information
exp_type = self.type3
try:
return self.generate_router(self, exp_type)
except NoRouteForTypeException:
raise NotImplementedError(exp_type)
def human_readable(self) -> HumanReadableRet:
return (
'{literal} : {type3}',
{
'literal': self.literal,
'type3': self.type3,
},
)
def __repr__(self) -> str:
return f'LiteralFitsConstraint({repr(self.type3)}, {repr(self.literal)}, comment={repr(self.comment)})'
class CanBeSubscriptedConstraint(ConstraintBase):
"""
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
"""
__slots__ = ('ret_type3', 'type3', 'index_type3', 'index_const', 'generate_router', )
ret_type3: PlaceholderForType
type3: PlaceholderForType
index_type3: PlaceholderForType
index_const: int | None
generate_router: TypeApplicationRouter['CanBeSubscriptedConstraint', CheckResult]
def __init__(
self,
context: Context,
ret_type3: PlaceholderForType,
type3: PlaceholderForType,
index_type3: PlaceholderForType,
index_const: int | None,
comment: Optional[str] = None,
) -> None:
super().__init__(context=context, comment=comment)
self.ret_type3 = ret_type3
self.type3 = type3
self.index_type3 = index_type3
self.index_const = index_const
def _generate_tuple(self, tp_args: tuple[Type3, ...]) -> CheckResult:
# We special case tuples to allow for ease of use to the programmer
# e.g. rather than having to do `fst a` and `snd a` and only have tuples of size 2
# we use a[0] and a[1] and allow for a[2] and on.
if self.index_const is None:
return Error('Must index with integer literal')
if self.index_const < 0 or len(tp_args) <= self.index_const:
return Error('Tuple index out of range')
return [
SameTypeConstraint(self.context, self.context.build.types['u32'], self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(self.context, tp_args[self.index_const], self.ret_type3, comment=f'Tuple subscript index {self.index_const}'),
]
def check(self) -> CheckResult:
if self.type3.resolve_as is None:
return RequireTypeSubstitutes()
exp_type = self.type3.resolve_as
if exp_type.application.constructor == self.context.build.tuple_:
return self._generate_tuple(exp_type.application.arguments)
result: NewConstraintList = []
result.extend([
MustImplementTypeClassConstraint(
self.context,
self.context.build.type_classes['Subscriptable'],
[exp_type],
),
SameTypeConstraint(
self.context,
self.context.build.types['u32'],
self.index_type3,
),
])
if isinstance(exp_type.application, (TypeApplication_Type, TypeApplication_TypeInt, )):
result.extend([
SameTypeConstraint(
self.context,
exp_type.application.arguments[0],
self.ret_type3,
),
])
# else: The MustImplementTypeClassConstraint will catch this
if exp_type.application.constructor == self.context.build.static_array:
_, sa_len = exp_type.application.arguments
if self.index_const is not None and (self.index_const < 0 or sa_len.value <= self.index_const):
return Error('Tuple index out of range')
return result
def human_readable(self) -> HumanReadableRet:
return (
'{type3}[{index}]',
{
'type3': self.type3,
'index': self.index_type3 if self.index_const is None else self.index_const,
},
)
def __repr__(self) -> str:
return f'CanBeSubscriptedConstraint({self.ret_type3!r}, {self.type3!r}, {self.index_type3!r}, {self.index_const!r}, comment={repr(self.comment)})'

View File

@ -1,334 +0,0 @@
"""
This module generates the typing constraints for Phasm.
The constraints solver can then try to resolve all constraints.
"""
from typing import Any, Generator, List
from .. import ourlang
from .constraints import (
CanBeSubscriptedConstraint,
ConstraintBase,
Context,
LiteralFitsConstraint,
MustImplementTypeClassConstraint,
SameFunctionArgumentConstraint,
SameTypeArgumentConstraint,
SameTypeConstraint,
TupleMatchConstraint,
)
from .functions import (
Constraint_TypeClassInstanceExists,
FunctionArgument,
FunctionSignature,
TypeVariable,
TypeVariableApplication_Unary,
TypeVariableContext,
)
from .placeholders import PlaceholderForType
from .types import Type3, TypeApplication_Struct, TypeConstructor_Function
ConstraintGenerator = Generator[ConstraintBase, None, None]
def phasm_type3_generate_constraints(inp: ourlang.Module[Any]) -> List[ConstraintBase]:
ctx = Context(inp.build)
return [*module(ctx, inp)]
def constant(ctx: Context, inp: ourlang.Constant, phft: PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)):
yield LiteralFitsConstraint(
ctx, phft, inp,
comment='The given literal must fit the expected type'
)
return
raise NotImplementedError(constant, inp)
def expression_binary_op(ctx: Context, inp: ourlang.BinaryOp, phft: PlaceholderForType) -> ConstraintGenerator:
return _expression_function_call(
ctx,
f'({inp.operator.name})',
inp.operator.signature,
[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.arguments,
inp,
phft,
)
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),
phft,
comment=f'typeOf("{inp.function.name}") == typeOf({inp.function.name})',
)
def _expression_function_call(
ctx: Context,
func_name: str,
signature: FunctionSignature,
arguments: list[ourlang.Expression],
return_expr: ourlang.Expression,
return_phft: PlaceholderForType,
) -> ConstraintGenerator:
"""
Generates all type-level constraints for a function call.
A Binary operator functions pretty much the same as a function call
with two arguments - it's only a syntactic difference.
"""
# First create placeholders for all arguments, and generate their constraints
arg_placeholders = {
arg_expr: PlaceholderForType([arg_expr])
for arg_expr in arguments
}
arg_placeholders[return_expr] = return_phft
for call_arg in arguments:
yield from expression(ctx, call_arg, arg_placeholders[call_arg])
# Then generate placeholders the function signature
# and apply constraints that the function requires
# Skip any fully reference types
# Making this a map ensures that if a function signature has
# the same type on multiple arguments, we only get one
# placeholder here. These don't need to update anything once
# subsituted - that's done by arg_placeholders.
type_var_map = {
x: PlaceholderForType([])
for x in signature.args
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
raise NotImplementedError(constraint)
func_var_map = {
x: PlaceholderForType([])
for x in signature.args
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
# 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([]))
yield SameFunctionArgumentConstraint(
ctx,
func_var_map[sig_arg],
sig_arg,
type_var_map,
comment=f'Ensure `{sig_arg.name}` matches in {signature}',
)
# 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
if isinstance(sig_arg, FunctionArgument):
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 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}',
)
# Lastly, tie the signature and expression together
for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, 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:
comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument'
if isinstance(sig_part, TypeVariable):
yield SameTypeConstraint(ctx, type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
continue
if isinstance(sig_part, Type3):
yield SameTypeConstraint(ctx, sig_part, arg_placeholders[arg_expr], comment=comment)
continue
if isinstance(sig_part, FunctionArgument):
yield SameTypeConstraint(ctx, func_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
continue
raise NotImplementedError(sig_part)
return
def expression(ctx: Context, inp: ourlang.Expression, phft: PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp, ourlang.Constant):
yield from constant(ctx, inp, phft)
return
if isinstance(inp, ourlang.VariableReference):
yield SameTypeConstraint(ctx, inp.variable.type3, phft,
comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})')
return
if isinstance(inp, ourlang.BinaryOp):
yield from expression_binary_op(ctx, inp, phft)
return
if isinstance(inp, ourlang.FunctionCall):
yield from expression_function_call(ctx, inp, phft)
return
if isinstance(inp, ourlang.FunctionReference):
yield from expression_function_reference(ctx, inp, phft)
return
if isinstance(inp, ourlang.TupleInstantiation):
r_type = []
for arg in inp.elements:
arg_phft = PlaceholderForType([arg])
yield from expression(ctx, arg, arg_phft)
r_type.append(arg_phft)
yield TupleMatchConstraint(
ctx,
phft,
r_type,
comment='The type of a tuple is a combination of its members'
)
return
if isinstance(inp, ourlang.Subscript):
varref_phft = PlaceholderForType([inp.varref])
index_phft = PlaceholderForType([inp.index])
yield from expression(ctx, inp.varref, varref_phft)
yield from expression(ctx, inp.index, index_phft)
if isinstance(inp.index, ourlang.ConstantPrimitive) and isinstance(inp.index.value, int):
yield CanBeSubscriptedConstraint(ctx, phft, varref_phft, index_phft, inp.index.value)
else:
yield CanBeSubscriptedConstraint(ctx, phft, varref_phft, index_phft, None)
return
if isinstance(inp, ourlang.AccessStructMember):
assert isinstance(inp.struct_type3.application, TypeApplication_Struct) # FIXME: See test_struct.py::test_struct_not_accessible
mem_typ = dict(inp.struct_type3.application.arguments)[inp.member]
yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO
yield SameTypeConstraint(ctx, mem_typ, phft,
comment=f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
return
raise NotImplementedError(expression, inp)
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
phft = PlaceholderForType([inp.value])
yield from expression(ctx, inp.value, phft)
yield SameTypeConstraint(ctx, fun.returns_type3, 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:
test_phft = PlaceholderForType([inp.test])
yield from expression(ctx, inp.test, test_phft)
yield SameTypeConstraint(ctx, test_phft, ctx.build.types['bool'],
comment='Must pass a boolean expression to if')
for stmt in inp.statements:
yield from statement(ctx, fun, stmt)
for stmt in inp.else_statements:
yield from statement(ctx, fun, stmt)
def statement(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement) -> ConstraintGenerator:
if isinstance(inp, ourlang.StatementReturn):
yield from statement_return(ctx, fun, inp)
return
if isinstance(inp, ourlang.StatementIf):
yield from statement_if(ctx, fun, inp)
return
raise NotImplementedError(statement, fun, inp)
def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator:
assert not inp.imported
if isinstance(inp, ourlang.StructConstructor):
return
for stmt in inp.statements:
yield from statement(ctx, inp, stmt)
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator:
phft = PlaceholderForType([inp.constant])
yield from constant(ctx, inp.constant, phft)
yield SameTypeConstraint(ctx, inp.type3, phft,
comment=f'The type of the value for module constant definition {inp.name} should match the type of that constant')
def module(ctx: Context, inp: ourlang.Module[Any]) -> ConstraintGenerator:
for cdef in inp.constant_defs.values():
yield from module_constant_def(ctx, cdef)
for func in inp.functions.values():
if func.imported:
continue
yield from function(ctx, func)

View File

@ -1,179 +0,0 @@
"""
Entry point to the type3 system
"""
from typing import Any, Dict, List
from .. import codestyle, ourlang
from .constraints import (
ConstraintBase,
Error,
HumanReadableRet,
RequireTypeSubstitutes,
SubstitutionMap,
)
from .constraintsgenerator import phasm_type3_generate_constraints
from .placeholders import (
PlaceholderForType,
Type3OrPlaceholder,
)
from .types import Type3
MAX_RESTACK_COUNT = 100
class Type3Exception(BaseException):
"""
Thrown when the Type3 system detects constraints that do not hold
"""
class DeducedType:
__slots__ = ('phft', 'typ', 'comment')
phft: PlaceholderForType
typ: Type3
comment: str
def __init__(self, phft: PlaceholderForType, typ: Type3) -> None:
self.phft = phft
self.typ = typ
self.comment = 'Deduced type'
def human_readable(self) -> HumanReadableRet:
return (
'{phft} == {typ}',
{
'phft': self.phft,
'typ': self.typ,
}
)
def phasm_type3(inp: ourlang.Module[Any], verbose: bool = False) -> None:
constraint_list = phasm_type3_generate_constraints(inp)
assert constraint_list
placeholder_substitutes: Dict[PlaceholderForType, Type3] = {}
placeholder_id_map: Dict[int, str] = {}
error_list: List[Error] = []
for _ in range(MAX_RESTACK_COUNT):
if verbose:
print()
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
old_constraint_ids = {id(x) for x in constraint_list}
old_placeholder_substitutes_len = len(placeholder_substitutes)
back_on_todo_list_count = 0
new_constraint_list = []
for constraint in constraint_list:
check_result = constraint.check()
if check_result is None:
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Constraint checks out')
continue
if isinstance(check_result, dict):
placeholder_substitutes.update(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Constraint checks out, and gave us new information')
continue
if isinstance(check_result, Error):
error_list.append(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print('-> Got an error')
continue
if isinstance(check_result, RequireTypeSubstitutes):
new_constraint_list.append(constraint)
back_on_todo_list_count += 1
continue
if isinstance(check_result, list):
new_constraint_list.extend(check_result)
if verbose:
print_constraint(placeholder_id_map, constraint)
print(f'-> Resulted in {len(check_result)} new constraints')
continue
raise NotImplementedError(constraint, check_result)
if verbose and 0 < back_on_todo_list_count:
print(f'{back_on_todo_list_count} constraints skipped for now')
if not new_constraint_list:
constraint_list = new_constraint_list
break
# Infinite loop detection
new_constraint_ids = {id(x) for x in new_constraint_list}
new_placeholder_substitutes_len = len(placeholder_substitutes)
if old_constraint_ids == new_constraint_ids and old_placeholder_substitutes_len == new_placeholder_substitutes_len:
if error_list:
raise Type3Exception(error_list)
raise Exception('Cannot type this program - not enough information')
constraint_list = new_constraint_list
if verbose:
print()
print_constraint_list(placeholder_id_map, constraint_list, placeholder_substitutes)
if constraint_list:
raise Exception(f'Cannot type this program - tried {MAX_RESTACK_COUNT} iterations')
if error_list:
raise Type3Exception(error_list)
for plh, typ in placeholder_substitutes.items():
for expr in plh.update_on_substitution:
expr.type3 = typ
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase | DeducedType) -> None:
txt, fmt = constraint.human_readable()
act_fmt: Dict[str, str] = {}
for fmt_key, fmt_val in fmt.items():
if isinstance(fmt_val, ourlang.Expression):
fmt_val = codestyle.expression(fmt_val)
if isinstance(fmt_val, Type3) or isinstance(fmt_val, PlaceholderForType):
fmt_val = get_printable_type_name(fmt_val, placeholder_id_map)
if not isinstance(fmt_val, str):
fmt_val = repr(fmt_val)
act_fmt[fmt_key] = fmt_val
if constraint.comment is not None:
print('- ' + txt.format(**act_fmt).ljust(40) + '; ' + constraint.comment)
else:
print('- ' + txt.format(**act_fmt))
def get_printable_type_name(inp: Type3OrPlaceholder, placeholder_id_map: Dict[int, str]) -> str:
if isinstance(inp, Type3):
return inp.name
if isinstance(inp, PlaceholderForType):
placeholder_id = id(inp)
if placeholder_id not in placeholder_id_map:
placeholder_id_map[placeholder_id] = 'T' + str(len(placeholder_id_map) + 1)
return placeholder_id_map[placeholder_id]
raise NotImplementedError(inp)
def print_constraint_list(placeholder_id_map: Dict[int, str], constraint_list: List[ConstraintBase], placeholder_substitutes: SubstitutionMap) -> None:
print('=== v type3 constraint_list v === ')
for psk, psv in placeholder_substitutes.items():
print_constraint(placeholder_id_map, DeducedType(psk, psv))
for constraint in constraint_list:
print_constraint(placeholder_id_map, constraint)
print('=== ^ type3 constraint_list ^ === ')

View File

@ -1,194 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Hashable, Iterable, List
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.
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 __hash__(self) -> int:
return hash((self.constructor, self.arguments, ))
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)
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})'
class TypeVariable:
"""
Types variable are used in function definition.
They are used in places where you don't know the exact type.
They are different from PlaceholderForType, as those are instanced
during type checking. These type variables are used solely in the
function's definition
"""
__slots__ = ('name', 'application', )
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 __hash__(self) -> int:
return hash((self.name, self.application, ))
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeVariable):
raise NotImplementedError
return (self.name == other.name
and self.application == other.application)
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.
"""
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.
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', )
name: str
def __init__(self, name: str) -> None:
self.name = name
def __hash__(self) -> int:
return hash((self.name, ))
def __eq__(self, other: Any) -> bool:
if other is None:
return False
if not isinstance(other, TypeConstructorVariable):
raise NotImplementedError(other)
return (self.name == other.name)
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})'
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.
"""
class ConstraintBase:
__slots__ = ()
class Constraint_TypeClassInstanceExists(ConstraintBase):
__slots__ = ('type_class3', 'types', )
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)
# 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 __repr__(self) -> str:
return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})'
class TypeVariableContext:
__slots__ = ('constraints', )
constraints: list[ConstraintBase]
def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None:
self.constraints = list(constraints)
def __copy__(self) -> 'TypeVariableContext':
return TypeVariableContext(self.constraints)
def __str__(self) -> str:
if not self.constraints:
return ''
return '(' + ', '.join(str(x) for x in self.constraints) + ') => '
def __repr__(self) -> str:
return f'TypeVariableContext({self.constraints!r})'
class FunctionArgument:
__slots__ = ('args', 'name', )
args: list[Type3 | TypeVariable]
name: str
def __init__(self, args: list[Type3 | TypeVariable]) -> None:
self.args = args
self.name = '(' + ' -> '.join(x.name for x in args) + ')'
class FunctionSignature:
__slots__ = ('context', 'args', )
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 __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})'

View File

@ -1,66 +0,0 @@
"""
Contains the placeholder for types for use in Phasm.
These are temporary while the compiler is calculating all the types and validating them.
"""
from typing import Any, Iterable, List, Optional, Protocol, Union
from .types import Type3
class ExpressionProtocol(Protocol):
"""
A protocol for classes that should be updated on substitution
"""
type3: Type3 | None
"""
The type to update
"""
class PlaceholderForType:
"""
A placeholder type, for when we don't know the final type yet
"""
__slots__ = ('update_on_substitution', 'resolve_as', )
update_on_substitution: List[ExpressionProtocol]
resolve_as: Optional[Type3]
def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None:
self.update_on_substitution = [*update_on_substitution]
self.resolve_as = None
def __repr__(self) -> str:
uos = ', '.join(repr(x) for x in self.update_on_substitution)
return f'PlaceholderForType({id(self)}, [{uos}])'
def __str__(self) -> str:
return f'PhFT_{id(self)}'
def __format__(self, format_spec: str) -> str:
if format_spec != 's':
raise TypeError('unsupported format string passed to Type3.__format__')
return str(self)
def __eq__(self, other: Any) -> bool:
if isinstance(other, Type3):
return False
if not isinstance(other, PlaceholderForType):
raise NotImplementedError
return self is other
def __ne__(self, other: Any) -> bool:
return not self.__eq__(other)
def __hash__(self) -> int:
return 0 # Valid but performs badly
def __bool__(self) -> bool:
raise NotImplementedError
Type3OrPlaceholder = Union[Type3, PlaceholderForType]

View File

@ -1,142 +0,0 @@
from typing import Any, Callable
from .functions import (
TypeConstructorVariable,
TypeVariable,
TypeVariableApplication_Nullary,
TypeVariableApplication_Unary,
)
from .typeclasses import Type3ClassArgs
from .types import (
KindArgument,
Type3,
TypeApplication_Type,
TypeApplication_TypeInt,
TypeConstructor_Base,
)
class NoRouteForTypeException(Exception):
pass
class TypeApplicationRouter[S, R]:
"""
Helper class to find a method based on a constructed type
"""
__slots__ = ('by_constructor', 'by_type', )
by_constructor: dict[Any, Callable[[S, Any], R]]
"""
Contains all the added routing functions for constructed types
"""
by_type: dict[Type3, Callable[[S], R]]
"""
Contains all the added routing functions for constructed types
"""
def __init__(self) -> None:
self.by_constructor = {}
self.by_type = {}
def add_n(self, typ: Type3, helper: Callable[[S], R]) -> None:
"""
Lets you route to types that were not constructed
Also known types of kind *
"""
self.by_type[typ] = helper
def add[T](self, constructor: TypeConstructor_Base[T], helper: Callable[[S, T], R]) -> None:
self.by_constructor[constructor] = helper
def __call__(self, arg0: S, typ: Type3) -> R:
t_helper = self.by_type.get(typ)
if t_helper is not None:
return t_helper(arg0)
c_helper = self.by_constructor.get(typ.application.constructor)
if c_helper is not None:
return c_helper(arg0, typ.application.arguments)
raise NoRouteForTypeException(arg0, typ)
TypeVariableLookup = tuple[
dict[TypeVariable, KindArgument],
dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
]
class TypeClassArgsRouter[S, R]:
"""
Helper class to find a method based on a type class argument list
"""
__slots__ = ('args', 'data', )
args: Type3ClassArgs
data: dict[tuple[Type3 | TypeConstructor_Base[Any], ...], Callable[[S, TypeVariableLookup], R]]
def __init__(self, args: Type3ClassArgs) -> None:
self.args = args
self.data = {}
def add(
self,
tv_map: dict[TypeVariable, Type3],
tc_map: dict[TypeConstructorVariable, TypeConstructor_Base[Any]],
helper: Callable[[S, TypeVariableLookup], R],
) -> None:
key: list[Type3 | TypeConstructor_Base[Any]] = []
for tc_arg in self.args:
if isinstance(tc_arg, TypeVariable):
key.append(tv_map[tc_arg])
else:
key.append(tc_map[tc_arg])
self.data[tuple(key)] = helper
def __call__(self, arg0: S, tv_map: dict[TypeVariable, Type3]) -> R:
key: list[Type3 | TypeConstructor_Base[Any]] = []
arguments: TypeVariableLookup = (dict(tv_map), {}, )
for tc_arg in self.args:
if isinstance(tc_arg, TypeVariable):
key.append(tv_map[tc_arg])
arguments[0][tc_arg] = tv_map[tc_arg]
continue
for tvar, typ in tv_map.items():
tvar_constructor = tvar.application.constructor
if tvar_constructor != tc_arg:
continue
key.append(typ.application.constructor)
arguments[1][tc_arg] = typ.application.constructor
if isinstance(tvar.application, TypeVariableApplication_Unary):
if isinstance(typ.application, TypeApplication_Type):
da_type, = typ.application.arguments
sa_type_tv = tvar.application.arguments
arguments[0][sa_type_tv] = da_type
continue
# FIXME: This feels sketchy. Shouldn't the type variable
# have the exact same number as arguments?
if isinstance(typ.application, TypeApplication_TypeInt):
sa_type, sa_len = typ.application.arguments
sa_type_tv = tvar.application.arguments
sa_len_tv = TypeVariable(sa_type_tv.name + '*', TypeVariableApplication_Nullary(None, None))
arguments[0][sa_type_tv] = sa_type
arguments[0][sa_len_tv] = sa_len
continue
raise NotImplementedError(tvar.application, typ.application)
t_helper = self.data.get(tuple(key))
if t_helper is not None:
return t_helper(arg0, arguments)
raise NoRouteForTypeException(arg0, tv_map)

View File

@ -1,104 +0,0 @@
from typing import Dict, Iterable, List, Mapping, Optional
from .functions import (
Constraint_TypeClassInstanceExists,
ConstraintBase,
FunctionSignature,
TypeConstructorVariable,
TypeVariable,
TypeVariableContext,
)
from .types import Type3
class Type3ClassMethod:
__slots__ = ('name', 'signature', )
name: str
signature: FunctionSignature
def __init__(self, name: str, signature: FunctionSignature) -> None:
self.name = name
self.signature = signature
def __str__(self) -> str:
return f'{self.name} :: {self.signature}'
def __repr__(self) -> str:
return f'Type3ClassMethod({repr(self.name)}, {repr(self.signature)})'
Type3ClassArgs = tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable]
class Type3Class:
__slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', )
name: str
args: Type3ClassArgs
methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod]
inherited_classes: List['Type3Class']
def __init__(
self,
name: str,
args: Type3ClassArgs,
methods: Mapping[str, Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]],
operators: Mapping[str, Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]],
inherited_classes: Optional[List['Type3Class']] = None,
additional_context: Optional[Mapping[str, Iterable[ConstraintBase]]] = None,
) -> None:
self.name = name
self.args = args
self.methods = {
k: Type3ClassMethod(k, _create_signature(v, self))
for k, v in methods.items()
}
self.operators = {
k: Type3ClassMethod(k, _create_signature(v, self))
for k, v in operators.items()
}
self.inherited_classes = inherited_classes or []
if additional_context:
for func_name, constraint_list in additional_context.items():
func = self.methods.get(func_name) or self.operators.get(func_name)
assert func is not None # type hint
func.signature.context.constraints.extend(constraint_list)
def __repr__(self) -> str:
return self.name
def _create_signature(
method_arg_list: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]],
type_class3: Type3Class,
) -> FunctionSignature:
context = TypeVariableContext()
if not isinstance(type_class3.args[0], TypeConstructorVariable):
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, type_class3.args))
signature_args: list[Type3 | TypeVariable | list[Type3 | TypeVariable]] = []
for method_arg in method_arg_list:
if isinstance(method_arg, Type3):
signature_args.append(method_arg)
continue
if isinstance(method_arg, list):
signature_args.append(method_arg)
continue
if isinstance(method_arg, TypeVariable):
type_constructor = method_arg.application.constructor
if type_constructor is None:
signature_args.append(method_arg)
continue
if (type_constructor, ) == type_class3.args:
context.constraints.append(Constraint_TypeClassInstanceExists(type_class3, [method_arg]))
signature_args.append(method_arg)
continue
raise NotImplementedError(method_arg)
return FunctionSignature(context, signature_args)

View File

@ -1,290 +0,0 @@
"""
Contains the final types for use in Phasm, as well as construtors.
"""
from typing import (
Any,
Hashable,
Self,
Tuple,
TypeVar,
)
S = TypeVar('S')
T = TypeVar('T')
class KindArgument:
pass
class TypeApplication_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
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 __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeApplication_Base):
raise NotImplementedError
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})'
class Type3(KindArgument):
"""
Base class for the type3 types
(Having a separate name makes it easier to distinguish from
Python's Type)
"""
__slots__ = ('name', 'application', )
name: str
"""
The name of the string, as parsed and outputted by codestyle.
"""
application: TypeApplication_Base[Any, Any]
"""
How the type was constructed; i.e. which constructor was used and which
type level arguments were applied to the constructor.
"""
def __init__(self, name: str, application: TypeApplication_Base[Any, Any]) -> None:
self.name = name
self.application = application
def __repr__(self) -> str:
return f'Type3({self.name!r}, {self.application!r})'
def __str__(self) -> str:
return self.name
def __format__(self, format_spec: str) -> str:
if format_spec != 's':
raise TypeError(f'unsupported format string passed to Type3.__format__: {format_spec}')
return str(self)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Type3):
raise NotImplementedError(other)
return self is other
def __ne__(self, other: Any) -> bool:
return not self.__eq__(other)
def __hash__(self) -> int:
return hash(self.name)
def __bool__(self) -> bool:
raise NotImplementedError
class TypeApplication_Nullary(TypeApplication_Base[None, None]):
"""
There was no constructor used to create this type - it's a 'simple' type like u32
"""
class IntType3(KindArgument):
"""
Sometimes you can have an int on the type level, e.g. when using static arrays
This is not the same as an int on the language level.
[1.0, 1.2] :: f32[2] :: * -> Int -> *
That is to say, you can create a static array of size two with each element
a f32 using f32[2].
"""
__slots__ = ('value', )
value: int
def __init__(self, value: int) -> None:
self.value = value
def __repr__(self) -> str:
return f'IntType3({self.value!r})'
def __format__(self, format_spec: str) -> str:
if format_spec != 's':
raise TypeError(f'unsupported format string passed to Type3.__format__: {format_spec}')
return str(self.value)
def __eq__(self, other: Any) -> bool:
if isinstance(other, IntType3):
return self.value == other.value
if isinstance(other, KindArgument):
return False
raise NotImplementedError
def __hash__(self) -> int:
return hash(self.value)
class TypeConstructor_Base[T]:
"""
Base class for type construtors
"""
__slots__ = ('name', '_cache', )
name: str
"""
The name of the type constructor
"""
_cache: dict[T, Type3]
"""
When constructing a type with the same arguments,
it should produce the exact same result.
"""
def __init__(self, name: str) -> None:
self.name = name
self._cache = {}
def make_name(self, key: T) -> str:
"""
Renders the type's name based on the given arguments
"""
raise NotImplementedError('make_name', self)
def make_application(self, key: T) -> TypeApplication_Base[Self, T]:
"""
Records how the type was constructed into type.
The type checker and compiler will need to know what
arguments where made to construct the type.
"""
raise NotImplementedError('make_application', self)
def construct(self, key: T) -> Type3:
"""
Constructs the type by applying the given arguments to this
constructor.
"""
result = self._cache.get(key, None)
if result is None:
self._cache[key] = result = Type3(self.make_name(key), self.make_application(key))
return result
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.name!r}, ...)'
class TypeConstructor_Type(TypeConstructor_Base[Tuple[Type3]]):
"""
Base class type constructors of kind: * -> *
Notably, static array.
"""
__slots__ = ()
def make_application(self, key: Tuple[Type3]) -> 'TypeApplication_Type':
return TypeApplication_Type(self, key)
def make_name(self, key: Tuple[Type3]) -> str:
return f'{self.name} {key[0].name} '
def __call__(self, arg0: Type3) -> Type3:
return self.construct((arg0, ))
class TypeApplication_Type(TypeApplication_Base[TypeConstructor_Type, Tuple[Type3]]):
pass
class TypeConstructor_TypeInt(TypeConstructor_Base[Tuple[Type3, IntType3]]):
"""
Base class type constructors of kind: * -> Int -> *
Notably, static array.
"""
__slots__ = ()
def make_application(self, key: Tuple[Type3, IntType3]) -> 'TypeApplication_TypeInt':
return TypeApplication_TypeInt(self, key)
def make_name(self, key: Tuple[Type3, IntType3]) -> str:
return f'{self.name} {key[0].name} {key[1].value}'
def __call__(self, arg0: Type3, arg1: IntType3) -> Type3:
return self.construct((arg0, arg1))
class TypeApplication_TypeInt(TypeApplication_Base[TypeConstructor_TypeInt, Tuple[Type3, IntType3]]):
pass
class TypeConstructor_TypeStar(TypeConstructor_Base[Tuple[Type3, ...]]):
"""
Base class type constructors of variadic kind
Notably, tuple.
"""
def make_application(self, key: Tuple[Type3, ...]) -> 'TypeApplication_TypeStar':
return TypeApplication_TypeStar(self, key)
def __call__(self, *args: Type3) -> Type3:
key: Tuple[Type3, ...] = tuple(args)
return self.construct(key)
class TypeApplication_TypeStar(TypeApplication_Base[TypeConstructor_TypeStar, Tuple[Type3, ...]]):
pass
class TypeConstructor_DynamicArray(TypeConstructor_Type):
def make_name(self, key: Tuple[Type3]) -> str:
if 'u8' == key[0].name:
return 'bytes'
return f'{key[0].name}[...]'
class TypeConstructor_StaticArray(TypeConstructor_TypeInt):
def make_name(self, key: Tuple[Type3, IntType3]) -> str:
return f'{key[0].name}[{key[1].value}]'
class TypeConstructor_Tuple(TypeConstructor_TypeStar):
def make_name(self, key: Tuple[Type3, ...]) -> str:
return '(' + ', '.join(x.name for x in key) + ', )'
class TypeConstructor_Function(TypeConstructor_TypeStar):
def make_name(self, key: Tuple[Type3, ...]) -> str:
return 'Callable[' + ', '.join(x.name for x in key) + ']'
class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]):
"""
Constructs struct types
"""
def make_application(self, key: tuple[tuple[str, Type3], ...]) -> 'TypeApplication_Struct':
return TypeApplication_Struct(self, key)
def make_name(self, key: tuple[tuple[str, Type3], ...]) -> str:
return f'{self.name}(' + ', '.join(
f'{n}: {t.name}'
for n, t in key
) + ')'
def construct(self, key: T) -> Type3:
"""
Constructs the type by applying the given arguments to this
constructor.
"""
raise Exception('This does not work with the caching system')
def __call__(self, name: str, args: tuple[tuple[str, Type3], ...]) -> Type3:
result = Type3(name, self.make_application(args))
return result
class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]):
pass

View File

@ -1,93 +0,0 @@
from .kindexpr import Star
from .record import Record
from .typeexpr import AtomicType, TypeApplication, TypeConstructor, TypeVariable
from .unify import unify
def main() -> None:
S = Star()
a_var = TypeVariable(name="a", kind=S)
b_var = TypeVariable(name="b", kind=S)
t_var = TypeVariable(name="t", kind=S >> S)
r_var = TypeVariable(name="r", kind=S >> S)
print(a_var)
print(b_var)
print(t_var)
print(r_var)
print()
u32_type = AtomicType(name="u32")
f64_type = AtomicType(name="f64")
print(u32_type)
print(f64_type)
print()
maybe_constructor = TypeConstructor(name="Maybe", kind=S >> S)
maybe_a_type = TypeApplication(constructor=maybe_constructor, argument=a_var)
maybe_u32_type = TypeApplication(constructor=maybe_constructor, argument=u32_type)
print(maybe_constructor)
print(maybe_a_type)
print(maybe_u32_type)
print()
either_constructor = TypeConstructor(name="Either", kind=S >> (S >> S))
either_a_constructor = TypeApplication(constructor=either_constructor, argument=a_var)
either_a_a_type = TypeApplication(constructor=either_a_constructor, argument=a_var)
either_u32_constructor = TypeApplication(constructor=either_constructor, argument=u32_type)
either_u32_f64_type = TypeApplication(constructor=either_u32_constructor, argument=f64_type)
either_u32_a_type = TypeApplication(constructor=either_u32_constructor, argument=a_var)
print(either_constructor)
print(either_a_constructor)
print(either_a_a_type)
print(either_u32_constructor)
print(either_u32_f64_type)
print(either_u32_a_type)
print()
t_a_type = TypeApplication(constructor=t_var, argument=a_var)
t_u32_type = TypeApplication(constructor=t_var, argument=u32_type)
print(t_a_type)
print(t_u32_type)
print()
shape_record = Record("Shape", (
("width", u32_type),
("height", either_u32_f64_type),
))
print('shape_record', shape_record)
print()
values = [
a_var,
b_var,
u32_type,
f64_type,
t_var,
t_a_type,
r_var,
maybe_constructor,
maybe_u32_type,
maybe_a_type,
either_u32_constructor,
either_u32_a_type,
either_u32_f64_type,
]
seen_exprs: set[str] = set()
for lft in values:
for rgt in values:
expr = f"{lft.name} ~ {rgt.name}"
if expr in seen_exprs:
continue
print(expr.ljust(40) + " => " + str(unify(lft, rgt)))
inv_expr = f"{rgt.name} ~ {lft.name}"
seen_exprs.add(inv_expr)
print()
if __name__ == '__main__':
main()

View File

@ -5,12 +5,10 @@ from typing import Any, Callable, Iterable, Protocol, Sequence
from ..build.base import BuildBase from ..build.base import BuildBase
from ..ourlang import SourceRef from ..ourlang import SourceRef
from ..type3 import types as type3types
from ..wasm import WasmTypeFloat32, WasmTypeFloat64, WasmTypeInt32, WasmTypeInt64 from ..wasm import WasmTypeFloat32, WasmTypeFloat64, WasmTypeInt32, WasmTypeInt64
from .kindexpr import KindExpr, Star from .kindexpr import KindExpr, Star
from .record import Record from .record import Record
from .typeexpr import ( from .typeexpr import (
AtomicType,
TypeExpr, TypeExpr,
TypeVariable, TypeVariable,
is_concrete, is_concrete,
@ -42,7 +40,6 @@ class Context:
def make_placeholder(self, arg: ExpressionProtocol | None = None, kind: KindExpr = Star(), prefix: str = 'p') -> TypeVariable: def make_placeholder(self, arg: ExpressionProtocol | None = None, kind: KindExpr = Star(), prefix: str = 'p') -> TypeVariable:
res = TypeVariable(kind, f"{prefix}_{len(self.placeholder_update)}") res = TypeVariable(kind, f"{prefix}_{len(self.placeholder_update)}")
self.placeholder_update[res] = arg self.placeholder_update[res] = arg
print('placeholder_update', res, arg)
return res return res
@dataclasses.dataclass @dataclasses.dataclass
@ -441,6 +438,10 @@ class TypeClassInstanceExistsConstraint(ConstraintBase):
if len(c_arg_list) != len(self.arg_list): if len(c_arg_list) != len(self.arg_list):
return skip_for_now() return skip_for_now()
if any(isinstance(x, Record) for x in c_arg_list):
# TODO: Allow users to implement type classes on their structs
return fail('Missing type class instance')
key = tuple(c_arg_list) key = tuple(c_arg_list)
existing_instances = self.ctx.build.type_class_instances[self.typeclass] existing_instances = self.ctx.build.type_class_instances[self.typeclass]
if key in existing_instances: if key in existing_instances:
@ -457,27 +458,3 @@ class TypeClassInstanceExistsConstraint(ConstraintBase):
def __str__(self) -> str: def __str__(self) -> str:
args = ' '.join(self.ctx.build.type5_name(x) for x in self.arg_list) args = ' '.join(self.ctx.build.type5_name(x) for x in self.arg_list)
return f'Exists {self.typeclass} {args}' return f'Exists {self.typeclass} {args}'
class RecordFoundException(Exception):
pass
def _type5_to_type3_or_type3_const(build: BuildBase[Any], type5: TypeExpr) -> type3types.Type3 | type3types.TypeConstructor_Base[Any] :
if isinstance(type5, Record):
raise RecordFoundException
if isinstance(type5, AtomicType):
return build.types[type5.name]
da_arg5 = build.type5_is_dynamic_array(type5)
if da_arg5 is not None:
return build.dynamic_array
sa_arg5 = build.type5_is_static_array(type5)
if sa_arg5 is not None:
return build.static_array
tp_arg5 = build.type5_is_tuple(type5)
if tp_arg5 is not None:
return build.tuple_
raise NotImplementedError(type5)

View File

@ -1,9 +1,6 @@
from typing import Any, Generator from typing import Any, Generator
from .. import ourlang from .. import ourlang
from ..type3 import functions as type3functions
from ..type3 import typeclasses as type3classes
from ..type3 import types as type3types
from .constraints import ( from .constraints import (
CanAccessStructMemberConstraint, CanAccessStructMemberConstraint,
CanBeSubscriptedConstraint, CanBeSubscriptedConstraint,
@ -16,8 +13,8 @@ from .constraints import (
TypeClassInstanceExistsConstraint, TypeClassInstanceExistsConstraint,
UnifyTypesConstraint, UnifyTypesConstraint,
) )
from .kindexpr import Star from .typeexpr import ConstrainedExpr, TypeApplication, TypeExpr, TypeVariable, instantiate, instantiate_constrained
from .typeexpr import TypeApplication, TypeExpr, TypeVariable, instantiate from ..typeclass import TypeClassConstraint
ConstraintGenerator = Generator[ConstraintBase, None, None] ConstraintGenerator = Generator[ConstraintBase, None, None]
@ -94,11 +91,7 @@ def expression_variable_reference(ctx: Context, inp: ourlang.VariableReference,
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.variable.type5, phft) yield UnifyTypesConstraint(ctx, inp.sourceref, inp.variable.type5, phft)
def expression_binary_operator(ctx: Context, inp: ourlang.BinaryOp, phft: TypeVariable) -> ConstraintGenerator: def expression_binary_operator(ctx: Context, inp: ourlang.BinaryOp, phft: TypeVariable) -> ConstraintGenerator:
yield from expression_function_call( yield from expression_function_call(ctx, _binary_op_to_function(inp), phft)
ctx,
_binary_op_to_function(ctx, inp),
phft,
)
def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: TypeVariable) -> ConstraintGenerator: def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: TypeVariable) -> ConstraintGenerator:
arg_typ_list = [] arg_typ_list = []
@ -107,20 +100,44 @@ def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: Type
yield from expression(ctx, arg, arg_tv) yield from expression(ctx, arg, arg_tv)
arg_typ_list.append(arg_tv) arg_typ_list.append(arg_tv)
assert isinstance(inp.function_instance.function.type5, TypeExpr) make_placeholder = lambda x, p: ctx.make_placeholder(kind=x, prefix=p)
inp.function_instance.type5 = ctx.make_placeholder(inp.function_instance)
yield UnifyTypesConstraint(ctx, inp.sourceref, instantiate(inp.function_instance.function.type5, {}, lambda x, p: ctx.make_placeholder(kind=x, prefix=p)), inp.function_instance.type5) ftp5 = inp.function_instance.function.type5
# constraints = [] assert ftp5 is not None
if isinstance(ftp5, ConstrainedExpr):
ftp5 = instantiate_constrained(ftp5, {}, make_placeholder)
for type_constraint in ftp5.constraints:
if isinstance(type_constraint, TypeClassConstraint):
yield TypeClassInstanceExistsConstraint(ctx, inp.sourceref, type_constraint.cls.name, type_constraint.variables)
continue
raise NotImplementedError(type_constraint)
ftp5 = ftp5.expr
else:
ftp5 = instantiate(ftp5, {}, make_placeholder)
phft2 = ctx.make_placeholder(inp.function_instance)
yield UnifyTypesConstraint(
ctx,
inp.sourceref,
ftp5,
phft2,
)
expr_type = ctx.build.type5_make_function(arg_typ_list + [phft]) expr_type = ctx.build.type5_make_function(arg_typ_list + [phft])
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.function_instance.type5, expr_type) yield UnifyTypesConstraint(ctx, inp.sourceref, phft2, expr_type)
# yield from constraints
def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: TypeVariable) -> ConstraintGenerator: def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: TypeVariable) -> ConstraintGenerator:
assert inp.function.type5 is not None # Todo: Make not nullable assert inp.function.type5 is not None # Todo: Make not nullable
yield UnifyTypesConstraint(ctx, inp.sourceref, inp.function.type5, phft) ftp5 = inp.function.type5
if isinstance(ftp5, ConstrainedExpr):
ftp5 = ftp5.expr
yield UnifyTypesConstraint(ctx, inp.sourceref, ftp5, phft)
def expression_tuple_instantiation(ctx: Context, inp: ourlang.TupleInstantiation, phft: TypeVariable) -> ConstraintGenerator: def expression_tuple_instantiation(ctx: Context, inp: ourlang.TupleInstantiation, phft: TypeVariable) -> ConstraintGenerator:
arg_typ_list = [] arg_typ_list = []
@ -196,7 +213,7 @@ def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement
assert args is not None assert args is not None
type5 = args[-1] type5 = args[-1]
else: else:
type5 = fun.type5 type5 = fun.type5.expr if isinstance(fun.type5, ConstrainedExpr) else fun.type5
yield from expression(ctx, inp.value, phft) yield from expression(ctx, inp.value, phft)
yield UnifyTypesConstraint(ctx, inp.sourceref, type5, phft) yield UnifyTypesConstraint(ctx, inp.sourceref, type5, phft)
@ -247,99 +264,13 @@ def module(ctx: Context, inp: ourlang.Module[Any]) -> ConstraintGenerator:
# TODO: Generalize? # TODO: Generalize?
def _binary_op_to_function(ctx: Context, inp: ourlang.BinaryOp) -> ourlang.FunctionCall: def _binary_op_to_function(inp: ourlang.BinaryOp) -> ourlang.FunctionCall:
""" """
Temporary method while migrating from type3 to type5 For typing purposes, a binary operator is just a function call.
It's only syntactic sugar - e.g. `1 + 2` vs `+(1, 2)`
""" """
assert inp.sourceref is not None # TODO: sourceref required assert inp.sourceref is not None # TODO: sourceref required
call = ourlang.FunctionCall(inp.operator, inp.sourceref) call = ourlang.FunctionCall(inp.operator, inp.sourceref)
call.arguments = [inp.left, inp.right] call.arguments = [inp.left, inp.right]
return call return call
class TypeVarMap:
__slots__ = ('ctx', 'cache', )
ctx: Context
cache: dict[type3functions.TypeVariable, TypeVariable | TypeApplication]
def __init__(self, ctx: Context):
self.ctx = ctx
self.cache = {}
def __getitem__(self, var: type3functions.TypeVariable) -> TypeVariable | TypeApplication:
exists = self.cache.get(var)
if exists is not None:
return exists
if isinstance(var.application, type3functions.TypeVariableApplication_Nullary):
res_var = self.ctx.make_placeholder()
self.cache[var] = res_var
return res_var
if isinstance(var.application, type3functions.TypeVariableApplication_Unary):
# TODO: t a -> t a
# solve by caching var.application.constructor in separate map
cvar = self.ctx.make_placeholder(kind=Star() >> Star())
avar = self.__getitem__(var.application.arguments)
res_app = TypeApplication(constructor=cvar, argument=avar)
self.cache[var] = res_app
return res_app
raise NotImplementedError(var)
def _signature_to_type5(
ctx: Context,
sourceref: ourlang.SourceRef,
signature: type3functions.FunctionSignature,
) -> tuple[TypeExpr, list[TypeClassInstanceExistsConstraint]]:
"""
Temporary hack while migrating from type3 to type5
"""
tv_map = TypeVarMap(ctx)
args: list[TypeExpr] = []
for t3arg in signature.args:
if isinstance(t3arg, type3types.Type3):
args.append(ctx.build.type5s[t3arg.name])
continue
if isinstance(t3arg, type3functions.TypeVariable):
args.append(tv_map[t3arg])
continue
if isinstance(t3arg, type3functions.FunctionArgument):
func_t3arg_list: list[TypeExpr] = []
for func_t3arg in t3arg.args:
if isinstance(func_t3arg, type3types.Type3):
func_t3arg_list.append(ctx.build.type5s[t3arg.name])
continue
if isinstance(func_t3arg, type3functions.TypeVariable):
func_t3arg_list.append(tv_map[func_t3arg])
continue
raise NotImplementedError
args.append(ctx.build.type5_make_function(func_t3arg_list))
continue
raise NotImplementedError(t3arg)
constraints: list[TypeClassInstanceExistsConstraint] = []
for const in signature.context.constraints:
if isinstance(const, type3functions.Constraint_TypeClassInstanceExists):
constraints.append(TypeClassInstanceExistsConstraint(
ctx,
sourceref,
const.type_class3.name,
[
tv_map[x]
for x in const.types
],
))
continue
raise NotImplementedError(const)
return (ctx.build.type5_make_function(args), constraints, )

View File

@ -125,6 +125,4 @@ def phasm_type5(inp: Module[Any], verbose: bool = False) -> None:
while isinstance(new_type5, TypeVariable): while isinstance(new_type5, TypeVariable):
new_type5 = placeholder_types[new_type5] new_type5 = placeholder_types[new_type5]
print('expression', expression)
print('new_type5', new_type5)
expression.type5 = new_type5 expression.type5 = new_type5

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import Callable, Sequence from typing import Callable, Iterable, Self, Sequence
from .kindexpr import Arrow, KindExpr, Nat, Star from .kindexpr import Arrow, KindExpr, Nat, Star
@ -38,8 +38,6 @@ class TypeVariable(TypeExpr):
""" """
A placeholder in a type expression A placeholder in a type expression
""" """
constraints: Sequence[Callable[[TypeExpr], bool]] = ()
def __hash__(self) -> int: def __hash__(self) -> int:
return hash((self.kind, self.name)) return hash((self.kind, self.name))
@ -53,7 +51,11 @@ class TypeApplication(TypeExpr):
constructor: TypeConstructor | TypeApplication | TypeVariable constructor: TypeConstructor | TypeApplication | TypeVariable
argument: TypeExpr argument: TypeExpr
def __init__(self, constructor: TypeConstructor | TypeApplication | TypeVariable, argument: TypeExpr) -> None: def __init__(
self,
constructor: TypeConstructor | TypeApplication | TypeVariable,
argument: TypeExpr,
) -> None:
if isinstance(constructor.kind, Star): if isinstance(constructor.kind, Star):
raise TypeError("A constructor cannot be a concrete type") raise TypeError("A constructor cannot be a concrete type")
@ -71,6 +73,23 @@ class TypeApplication(TypeExpr):
self.constructor = constructor self.constructor = constructor
self.argument = argument self.argument = argument
class TypeConstraint:
"""
Base class for type contraints
"""
__slots__ = ()
def __str__(self) -> str:
raise NotImplementedError
def instantiate(self, known_map: dict[TypeVariable, TypeVariable], make_variable: Callable[[KindExpr, str], TypeVariable]) -> Self:
raise NotImplementedError
@dataclass
class ConstrainedExpr:
expr: TypeExpr
constraints: tuple[TypeConstraint, ...]
def occurs(lft: TypeVariable, rgt: TypeApplication) -> bool: def occurs(lft: TypeVariable, rgt: TypeApplication) -> bool:
""" """
Checks whether the given variable occurs in the given application. Checks whether the given variable occurs in the given application.
@ -152,6 +171,14 @@ def instantiate(
known_map: dict[TypeVariable, TypeVariable], known_map: dict[TypeVariable, TypeVariable],
make_variable: Callable[[KindExpr, str], TypeVariable], make_variable: Callable[[KindExpr, str], TypeVariable],
) -> TypeExpr: ) -> TypeExpr:
"""
Make a copy of all variables.
This is need when you use a polymorphic function in two places. In the
one place, `t a` may refer to `u32[...]` and in another to `f32[4]`.
These should not be unified since they refer to a different monomorphic
version of the function.
"""
if isinstance(expr, AtomicType): if isinstance(expr, AtomicType):
return expr return expr
@ -176,3 +203,18 @@ def instantiate(
) )
raise NotImplementedError(expr) raise NotImplementedError(expr)
def instantiate_constrained(
constrainedexpr: ConstrainedExpr,
known_map: dict[TypeVariable, TypeVariable],
make_variable: Callable[[KindExpr, str], TypeVariable],
) -> ConstrainedExpr:
"""
Instantiates a type expression and its constraints
"""
expr = instantiate(constrainedexpr.expr, known_map, make_variable)
constraints = tuple(
x.instantiate(known_map, make_variable)
for x in constrainedexpr.constraints
)
return ConstrainedExpr(expr, constraints)

View File

@ -1,8 +1,38 @@
from dataclasses import dataclass import dataclasses
from typing import Callable, Self
from ..type5.typeexpr import TypeVariable from ..type5.kindexpr import KindExpr
from ..type5.typeexpr import TypeConstraint, TypeExpr, TypeVariable, instantiate
@dataclass @dataclasses.dataclass
class TypeClass: class TypeClass:
name: str name: str
variables: list[TypeVariable] variables: list[TypeVariable]
methods: dict[str, TypeExpr] = dataclasses.field(default_factory=dict)
operators: dict[str, TypeExpr] = dataclasses.field(default_factory=dict)
class TypeClassConstraint(TypeConstraint):
__slots__ = ('cls', 'variables', )
def __init__(self, cls: TypeClass, variables: list[TypeVariable]) -> None:
self.cls = cls
self.variables = variables
def __str__(self) -> str:
vrs = ' '.join(x.name for x in self.variables)
return f'{self.cls.name} {vrs}'
def __repr__(self) -> str:
vrs = ', '.join(str(x) for x in self.variables)
return f'TypeClassConstraint({self.cls.name}, [{vrs}])'
def instantiate(self, known_map: dict[TypeVariable, TypeVariable], make_variable: Callable[[KindExpr, str], TypeVariable]) -> Self:
return TypeClassConstraint(
self.cls,
[
instantiate(var, known_map, make_variable)
for var in self.variables
]
)

View File

@ -1,13 +1,13 @@
from __future__ import annotations
import os import os
import struct import struct
import sys import sys
from typing import Any, Callable, Generator, Iterable, List, TextIO, Union from typing import Any, Callable, Generator, Iterable, List, TextIO, Union
from phasm import compiler from phasm import compiler
from phasm.build import builtins
from phasm.codestyle import phasm_render from phasm.codestyle import phasm_render
from phasm.type3 import types as type3types from phasm.type5.typeexpr import TypeExpr
from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter
from phasm.wasm import ( from phasm.wasm import (
WasmTypeFloat32, WasmTypeFloat32,
WasmTypeFloat64, WasmTypeFloat64,
@ -17,6 +17,7 @@ from phasm.wasm import (
) )
from . import runners from . import runners
from . import memory
DASHES = '-' * 16 DASHES = '-' * 16
@ -115,7 +116,10 @@ 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] func = runner.phasm_ast.functions[func_name]
func_args = runner.phasm_ast.build.type5_is_function(func.type5)
assert func_args is not None
func_ret = func_args.pop()
if len(func_args) != len(args): if len(func_args) != len(args):
raise RuntimeError(f'Invalid number of args for {func_name}') raise RuntimeError(f'Invalid number of args for {func_name}')
@ -151,11 +155,8 @@ class Suite:
result = SuiteResult() result = SuiteResult()
result.returned_value = runner.call(func_name, *wasm_args) result.returned_value = runner.call(func_name, *wasm_args)
result.returned_value = _load_memory_stored_returned_value( loader = memory.Loader(runner.phasm_ast.build, runner)(func_ret)
runner, result.returned_value = loader(result.returned_value)
func_name,
result.returned_value,
)
if verbose: if verbose:
write_header(sys.stderr, 'Memory (post run)') write_header(sys.stderr, 'Memory (post run)')
@ -169,7 +170,7 @@ def write_header(textio: TextIO, msg: str) -> None:
def _write_memory_stored_value( def _write_memory_stored_value(
runner: runners.RunnerBase, runner: runners.RunnerBase,
adr: int, adr: int,
val_typ: type3types.Type3, val_typ: TypeExpr,
val: Any, val: Any,
) -> int: ) -> int:
try: try:
@ -197,7 +198,7 @@ def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> in
runner.interpreter_write_memory(adr + 4, val) runner.interpreter_write_memory(adr + 4, val)
return adr return adr
def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any], da_args: tuple[type3types.Type3]) -> int: def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any], da_args: tuple[TypeExpr]) -> int:
runner, val = attrs runner, val = attrs
da_type, = da_args da_type, = da_args
@ -219,7 +220,7 @@ def _allocate_memory_stored_dynamic_array(attrs: tuple[runners.RunnerBase, Any],
offset += _write_memory_stored_value(runner, offset, da_type, val_el_val) offset += _write_memory_stored_value(runner, offset, da_type, val_el_val)
return adr return adr
def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> int: def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any], sa_args: tuple[TypeExpr, type3types.IntType3]) -> int:
runner, val = attrs runner, val = attrs
sa_type, sa_len = sa_args sa_type, sa_len = sa_args
@ -239,7 +240,7 @@ def _allocate_memory_stored_static_array(attrs: tuple[runners.RunnerBase, Any],
offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val) offset += _write_memory_stored_value(runner, offset, sa_type, val_el_val)
return adr return adr
def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_args: tuple[tuple[str, type3types.Type3], ...]) -> int: def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_args: tuple[tuple[str, TypeExpr], ...]) -> int:
runner, val = attrs runner, val = attrs
assert isinstance(val, dict) assert isinstance(val, dict)
@ -259,7 +260,7 @@ def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_arg
return adr return adr
def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args: tuple[type3types.Type3, ...]) -> int: def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args: tuple[TypeExpr, ...]) -> int:
runner, val = attrs runner, val = attrs
assert isinstance(val, tuple) assert isinstance(val, tuple)
@ -276,12 +277,6 @@ def _allocate_memory_stored_tuple(attrs: tuple[runners.RunnerBase, Any], tp_args
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr return adr
ALLOCATE_MEMORY_STORED_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, Any], Any]()
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.dynamic_array, _allocate_memory_stored_dynamic_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.static_array, _allocate_memory_stored_static_array)
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.struct, _allocate_memory_stored_struct)
ALLOCATE_MEMORY_STORED_ROUTER.add(builtins.tuple_, _allocate_memory_stored_tuple)
def _load_memory_stored_returned_value( def _load_memory_stored_returned_value(
runner: runners.RunnerBase, runner: runners.RunnerBase,
func_name: str, func_name: str,
@ -316,7 +311,7 @@ def _load_memory_stored_returned_value(
return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3) return LOAD_FROM_ADDRESS_ROUTER((runner, wasm_value), ret_type3)
def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> Any: def _unpack(runner: runners.RunnerBase, typ: TypeExpr, inp: bytes) -> Any:
typ_info = runner.phasm_ast.build.type_info_map.get(typ.name) typ_info = runner.phasm_ast.build.type_info_map.get(typ.name)
if typ_info is None: if typ_info is None:
@ -377,7 +372,7 @@ def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator
yield all_bytes[offset:offset + size] yield all_bytes[offset:offset + size]
offset += size offset += size
def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_args: tuple[type3types.Type3]) -> Any: def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_args: tuple[TypeExpr]) -> Any:
runner, adr = attrs runner, adr = attrs
da_type, = da_args da_type, = da_args
@ -400,7 +395,7 @@ def _load_dynamic_array_from_address(attrs: tuple[runners.RunnerBase, int], da_a
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes) for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
) )
def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_args: tuple[type3types.Type3, type3types.IntType3]) -> Any: def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_args: tuple[TypeExpr, type3types.IntType3]) -> Any:
runner, adr = attrs runner, adr = attrs
sub_typ, len_typ = sa_args sub_typ, len_typ = sa_args
@ -418,7 +413,7 @@ def _load_static_array_from_address(attrs: tuple[runners.RunnerBase, int], sa_ar
for arg_bytes in _split_read_bytes(read_bytes, arg_sizes) for arg_bytes in _split_read_bytes(read_bytes, arg_sizes)
) )
def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tuple[tuple[str, type3types.Type3], ...]) -> dict[str, Any]: def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tuple[tuple[str, TypeExpr], ...]) -> dict[str, Any]:
runner, adr = attrs runner, adr = attrs
sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n') sys.stderr.write(f'Reading 0x{adr:08x} struct {list(st_args)}\n')
@ -435,7 +430,7 @@ def _load_struct_from_address(attrs: tuple[runners.RunnerBase, int], st_args: tu
for (arg_name, arg_typ, ), arg_bytes in zip(st_args, _split_read_bytes(read_bytes, arg_sizes), strict=True) for (arg_name, arg_typ, ), arg_bytes in zip(st_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
} }
def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tuple[type3types.Type3, ...]) -> Any: def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tuple[TypeExpr, ...]) -> Any:
runner, adr = attrs runner, adr = attrs
sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(tp_args)}\n') sys.stderr.write(f'Reading 0x{adr:08x} tuple {len(tp_args)}\n')
@ -451,9 +446,3 @@ def _load_tuple_from_address(attrs: tuple[runners.RunnerBase, int], tp_args: tup
_unpack(runner, arg_typ, arg_bytes) _unpack(runner, arg_typ, arg_bytes)
for arg_typ, arg_bytes in zip(tp_args, _split_read_bytes(read_bytes, arg_sizes), strict=True) for arg_typ, arg_bytes in zip(tp_args, _split_read_bytes(read_bytes, arg_sizes), strict=True)
) )
LOAD_FROM_ADDRESS_ROUTER = TypeApplicationRouter[tuple[runners.RunnerBase, int], Any]()
LOAD_FROM_ADDRESS_ROUTER.add(builtins.dynamic_array, _load_dynamic_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(builtins.static_array, _load_static_array_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(builtins.struct, _load_struct_from_address)
LOAD_FROM_ADDRESS_ROUTER.add(builtins.tuple_, _load_tuple_from_address)

134
tests/integration/memory.py Normal file
View File

@ -0,0 +1,134 @@
from typing import Any, Callable, Protocol, TypeAlias
import struct
from phasm.build.base import BuildBase
from phasm.build.typerouter import BuildTypeRouter
from phasm.type5.typeexpr import AtomicType, TypeExpr
from phasm.wasm import (
WasmTypeFloat32,
WasmTypeFloat64,
WasmTypeInt32,
WasmTypeInt64,
WasmTypeNone,
)
class MemoryAccess(Protocol):
def interpreter_read_memory(self, offset: int, length: int) -> bytes:
pass
class LoaderFunc(Protocol):
alloc_size: int
def __call__(self, wasm_value: Any) -> Any:
"""
Takes a WASM value and returns a Python value
Based on the phasm type
"""
class Loader(BuildTypeRouter[LoaderFunc]):
__slots__ = ('access', )
access: MemoryAccess
def __init__(self, build: BuildBase[Any], access: MemoryAccess) -> None:
super().__init__(build)
self.access = access
def when_atomic(self, typ: AtomicType) -> LoaderFunc:
type_info = self.build.type_info_map[typ.name]
if type_info.wasm_type is WasmTypeNone:
raise NotImplementedError()
if type_info.wasm_type is WasmTypeInt32 or type_info.wasm_type is WasmTypeInt64:
if type_info.signed is None:
raise NotImplementedError
return IntLoader(type_info.signed, type_info.alloc_size)
if type_info.wasm_type is WasmTypeFloat32 or type_info.wasm_type is WasmTypeFloat64:
return FloatLoader(type_info.alloc_size)
raise NotImplementedError(typ)
def when_dynamic_array(self, da_arg: TypeExpr) -> LoaderFunc:
if da_arg.name == 'u8':
return BytesLoader(self.access)
return DynamicArrayLoader(self.access, self(da_arg))
class IntLoader:
__slots__ = ('alloc_size', 'signed', )
def __init__(self, signed: bool, alloc_size: int) -> None:
self.signed = signed
self.alloc_size = alloc_size
def __call__(self, wasm_value: Any) -> Any:
assert isinstance(wasm_value, int), wasm_value
# Work around the fact that phasm has unsigned integers but wasm does not
data = wasm_value.to_bytes(8, 'big', signed=True)
data = data[-self.alloc_size:]
wasm_value = int.from_bytes(data, 'big', signed=self.signed)
return wasm_value
class FloatLoader:
__slots__ = ('alloc_size', )
def __init__(self, alloc_size: int) -> None:
self.alloc_size = alloc_size
def __call__(self, wasm_value: Any) -> Any:
assert isinstance(wasm_value, float), wasm_value
return wasm_value
class DynamicArrayLoader:
__slots__ = ('access', 'alloc_size', 'sub_loader', )
access: MemoryAccess
alloc_size: int
sub_loader: LoaderFunc
def __init__(self, access: MemoryAccess, sub_loader: LoaderFunc) -> None:
self.access = access
self.sub_loader = sub_loader
def __call__(self, wasm_value: Any) -> Any:
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
# wasm_value must be a pointer
# The first value at said pointer is the length of the array
read_bytes = self.access.interpreter_read_memory(adr, 4)
array_len, = struct.unpack('<I', read_bytes)
adr += 4
raise NotImplementedError
class BytesLoader:
__slots__ = ('access', 'alloc_size', )
access: MemoryAccess
alloc_size: int
def __init__(self, access: MemoryAccess) -> None:
self.access = access
def __call__(self, wasm_value: Any) -> bytes:
assert isinstance(wasm_value, int), wasm_value
adr = wasm_value
del wasm_value
# wasm_value must be a pointer
# The first value at said pointer is the length of the array
read_bytes = self.access.interpreter_read_memory(adr, 4)
array_len, = struct.unpack('<I', read_bytes)
adr += 4
return self.access.interpreter_read_memory(adr, array_len)

View File

@ -10,7 +10,6 @@ from phasm import ourlang, wasm
from phasm.compiler import phasm_compile from phasm.compiler import phasm_compile
from phasm.optimise.removeunusedfuncs import removeunusedfuncs from phasm.optimise.removeunusedfuncs import removeunusedfuncs
from phasm.parser import phasm_parse from phasm.parser import phasm_parse
from phasm.type3.entry import phasm_type3
from phasm.type5.solver import phasm_type5 from phasm.type5.solver import phasm_type5
from phasm.wasmgenerator import Generator as WasmGenerator from phasm.wasmgenerator import Generator as WasmGenerator