From a3d9b8af0af66925a47aad11b4816f2480559566 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sun, 3 Aug 2025 13:14:48 +0200 Subject: [PATCH] Cleanup --- Makefile | 2 +- phasm/__main__.py | 4 +- phasm/build/base.py | 328 +++---------- phasm/build/builtins.py | 26 - phasm/build/default.py | 62 +-- phasm/build/typeclasses/eq.py | 47 +- phasm/build/typeclasses/floating.py | 24 +- phasm/build/typevariablerouter.py | 56 +-- phasm/compiler.py | 66 ++- phasm/ourlang.py | 85 ++-- phasm/parser.py | 119 +---- phasm/type3/__init__.py | 0 phasm/type3/constraints.py | 704 ---------------------------- phasm/type3/constraintsgenerator.py | 334 ------------- phasm/type3/entry.py | 179 ------- phasm/type3/functions.py | 194 -------- phasm/type3/placeholders.py | 66 --- phasm/type3/routers.py | 142 ------ phasm/type3/typeclasses.py | 104 ---- phasm/type3/types.py | 290 ------------ phasm/type5/__main__.py | 93 ---- phasm/type5/constraints.py | 30 +- phasm/type5/fromast.py | 147 ++---- phasm/type5/typeexpr.py | 52 +- phasm/typeclass/__init__.py | 40 +- tests/integration/helpers.py | 51 +- tests/integration/memory.py | 134 ++++++ tests/integration/runners.py | 1 - 28 files changed, 536 insertions(+), 2844 deletions(-) delete mode 100644 phasm/build/builtins.py delete mode 100644 phasm/type3/__init__.py delete mode 100644 phasm/type3/constraints.py delete mode 100644 phasm/type3/constraintsgenerator.py delete mode 100644 phasm/type3/entry.py delete mode 100644 phasm/type3/functions.py delete mode 100644 phasm/type3/placeholders.py delete mode 100644 phasm/type3/routers.py delete mode 100644 phasm/type3/typeclasses.py delete mode 100644 phasm/type3/types.py delete mode 100644 phasm/type5/__main__.py create mode 100644 tests/integration/memory.py diff --git a/Makefile b/Makefile index 748a0f4..40e394b 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ lint: venv/.done venv/bin/ruff check phasm tests 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 python3.12 -m venv venv diff --git a/phasm/__main__.py b/phasm/__main__.py index a4e6a31..4c7e2b1 100644 --- a/phasm/__main__.py +++ b/phasm/__main__.py @@ -7,7 +7,7 @@ import sys from .compiler import phasm_compile from .optimise.removeunusedfuncs import removeunusedfuncs from .parser import phasm_parse -from .type3.entry import phasm_type3 +from .type5.solver import phasm_type5 def main(source: str, sink: str) -> int: @@ -19,7 +19,7 @@ def main(source: str, sink: str) -> int: code_py = fil.read() our_module = phasm_parse(code_py) - phasm_type3(our_module, verbose=False) + phasm_type5(our_module, verbose=False) wasm_module = phasm_compile(our_module) removeunusedfuncs(wasm_module) code_wat = wasm_module.to_wat() diff --git a/phasm/build/base.py b/phasm/build/base.py index fb49486..c969f78 100644 --- a/phasm/build/base.py +++ b/phasm/build/base.py @@ -6,32 +6,11 @@ Contains nothing but the explicit compiler builtins. from typing import Any, Callable, NamedTuple, Sequence, Type from warnings import warn -from ..type3.functions import ( - TypeConstructorVariable, - TypeVariable, -) -from ..type3.routers import ( - NoRouteForTypeException, - TypeApplicationRouter, - TypeClassArgsRouter, - TypeVariableLookup, -) 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 record as type5record from ..type5 import typeexpr as type5typeexpr from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone -from . import builtins from .typerouter import TypeAllocSize, TypeName from .typevariablerouter import TypeVariableRouter @@ -59,102 +38,105 @@ class MissingImplementationWarning(Warning): class BuildBase[G]: __slots__ = ( - 'dynamic_array', 'dynamic_array_type5_constructor', - 'function', 'function_type5_constructor', - 'static_array', 'static_array_type5_constructor', - 'struct', - 'tuple_', 'tuple_type5_constructor_map', - 'none_', 'none_type5', 'unit_type5', - 'bool_', 'bool_type5', 'u8_type5', 'u32_type5', + 'bytes_type5', 'type_info_map', 'type_info_constructed', 'types', - 'type5s', 'type_classes', 'type_class_instances', - 'type_class_instance_methods', 'methods', 'operators', 'type5_name', 'type5_alloc_size_root', '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 - - 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 - - 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 - determined length, and each argument is the same. + See type5_make_function and type5_is_function. """ + 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 - """ - 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. + See type5_make_static_array and type5_is_static_array. """ tuple_type5_constructor_map: dict[int, type5typeexpr.TypeConstructor] + """ + Map for constructors for tuples of each length. - none_: Type3 - """ - The none type, for when functions simply don't return anything. e.g., IO(). + See type5_make_tuple and type5_is_tuple. """ + none_type5: type5typeexpr.AtomicType + """ + The none type. + + TODO: Not sure this should be a buildin (rather than a Maybe type). + """ 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 + """ + The bool type, either True or False. + + Builtin since functions require a boolean value in their test. + """ 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 + """ + 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] """ @@ -170,12 +152,7 @@ class BuildBase[G]: not memory pointers but table addresses instead. """ - types: dict[str, Type3] - """ - Types that are available without explicit import. - """ - - type5s: dict[str, type5typeexpr.TypeExpr] + types: dict[str, type5typeexpr.TypeExpr] """ 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_instance_methods: dict[tuple[TypeClass, str], TypeClassArgsRouter[G, None]] - """ - Methods (and operators) for type class instances that are available without explicit import. - """ - - methods: dict[str, tuple[type5typeexpr.TypeExpr, TypeVariableRouter[G]]] + methods: dict[str, tuple[type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr, TypeVariableRouter[G]]] """ 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. """ @@ -224,163 +196,68 @@ class BuildBase[G]: 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: - 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() 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.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.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.unit_type5 = type5typeexpr.AtomicType('()') + self.bool_type5 = type5typeexpr.AtomicType('bool') self.u8_type5 = type5typeexpr.AtomicType('u8') self.u32_type5 = type5typeexpr.AtomicType('u32') + self.bytes_type5 = self.type5_make_dynamic_array(self.u8_type5) 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), - '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 = { - 'None': self.none_, - 'bool': self.bool_, - } - self.type5s = { + 'None': self.none_type5, + '()': self.unit_type5, 'bool': self.bool_type5, 'u8': self.u8_type5, 'u32': self.u32_type5, + 'bytes': self.bytes_type5, } self.type_classes = {} self.type_class_instances = {} - self.type_class_instance_methods = {} self.methods = {} 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_alloc_size_root = TypeAllocSize(self, is_member=False) 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: assert cls.name not in self.type_classes, 'Duplicate typeclass name' self.type_classes[cls.name] = cls self.type_class_instances[cls.name] = set() - def register_type_class_method( - self, - cls: TypeClass, - name: str, - type: type5typeexpr.TypeExpr, - ) -> None: - assert name not in self.methods, 'Duplicate typeclass method name' + for mtd_nam, mtd_typ in cls.methods.items(): + assert mtd_nam not in self.methods, 'Duplicate typeclass method name' + self.methods[mtd_nam] = (mtd_typ, TypeVariableRouter(), ) - 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( self, cls: TypeClass, *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: self.type_class_instances[cls.name].add(tuple(args)) @@ -390,61 +267,9 @@ class BuildBase[G]: _, mtd_rtr = self.methods[mtd_nam] mtd_rtr.register(cls.variables, args, mtd_imp) - def calculate_alloc_size_static_array(self, args: tuple[Type3, IntType3]) -> int: - """ - Helper method for calculate_alloc_size - static_array - """ - 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') + for opr_nam, opr_imp in operators.items(): + _, opr_rtr = self.operators[opr_nam] + opr_rtr.register(cls.variables, args, opr_imp) def type5_make_function(self, args: Sequence[type5typeexpr.TypeExpr]) -> type5typeexpr.TypeExpr: if not args: @@ -476,7 +301,10 @@ class BuildBase[G]: 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): return None if not isinstance(typeexpr.constructor, type5typeexpr.TypeApplication): diff --git a/phasm/build/builtins.py b/phasm/build/builtins.py deleted file mode 100644 index 5dd65d5..0000000 --- a/phasm/build/builtins.py +++ /dev/null @@ -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)) - diff --git a/phasm/build/default.py b/phasm/build/default.py index 6a5cc70..a165f8c 100644 --- a/phasm/build/default.py +++ b/phasm/build/default.py @@ -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. """ -from ..type3.types import ( - Type3, - TypeApplication_Nullary, -) from ..type5 import typeexpr as type5typeexpr from ..wasm import ( WasmTypeFloat32, @@ -21,21 +17,21 @@ from ..wasm import ( from ..wasmgenerator import Generator from .base import BuildBase, TypeInfo from .typeclasses import ( - bits, - convertable, + # bits, + # convertable, eq, - extendable, + # extendable, floating, - foldable, - fractional, - integral, - intnum, - natnum, - ord, - promotable, - reinterpretable, - sized, - subscriptable, + # foldable, + # fractional, + # integral, + # intnum, + # natnum, + # ord, + # promotable, + # reinterpretable, + # sized, + # subscriptable, ) @@ -45,23 +41,8 @@ class BuildDefault(BuildBase[Generator]): def __init__(self) -> None: 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({ - 'u8': TypeInfo('u8', WasmTypeInt32, 'i32.load8_u', 'i32.store8', 1, 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), 'i8': TypeInfo('i8', WasmTypeInt32, 'i32.load8_s', 'i32.store8', 1, True), 'i16': TypeInfo('i16', WasmTypeInt32, 'i32.load16_s', 'i32.store16', 2, True), @@ -72,20 +53,6 @@ class BuildDefault(BuildBase[Generator]): }) 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'), 'u64': type5typeexpr.AtomicType('u64'), 'i8': type5typeexpr.AtomicType('i8'), @@ -94,13 +61,12 @@ class BuildDefault(BuildBase[Generator]): 'i64': type5typeexpr.AtomicType('i64'), 'f32': type5typeexpr.AtomicType('f32'), 'f64': type5typeexpr.AtomicType('f64'), - 'bytes': type5typeexpr.TypeApplication(self.dynamic_array_type5_constructor, self.u8_type5), }) tc_list = [ floating, # bits, - # eq, ord, + eq, # ord, # extendable, promotable, # convertable, reinterpretable, # natnum, intnum, fractional, floating, diff --git a/phasm/build/typeclasses/eq.py b/phasm/build/typeclasses/eq.py index 7acc6b4..8a2d4e2 100644 --- a/phasm/build/typeclasses/eq.py +++ b/phasm/build/typeclasses/eq.py @@ -1,23 +1,34 @@ """ The Eq type class is defined for types that can be compered based on equality. """ +from __future__ import annotations + from typing import Any -from ...type3.functions import make_typevar -from ...type3.routers import TypeVariableLookup -from ...type3.typeclasses import Type3Class +from ...type5.kindexpr import Star +from ...type5.typeexpr import ConstrainedExpr, TypeVariable +from ...typeclass import TypeClass, TypeClassConstraint from ...wasmgenerator import Generator as WasmGenerator from ..base import BuildBase def load(build: BuildBase[Any]) -> None: - a = make_typevar('a') + a = TypeVariable(kind=Star(), name='a') - Eq = Type3Class('Eq', (a, ), methods={}, operators={ - '==': [a, a, build.bool_], - '!=': [a, a, build.bool_], + Eq = TypeClass('Eq', (a, ), methods={}, operators={}) + + 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? - }) + } 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: 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_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_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_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_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_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_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_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_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_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_not_equals, }) diff --git a/phasm/build/typeclasses/floating.py b/phasm/build/typeclasses/floating.py index 10ce0ac..0f7ea8a 100644 --- a/phasm/build/typeclasses/floating.py +++ b/phasm/build/typeclasses/floating.py @@ -1,11 +1,12 @@ """ The Floating type class is defined for Real numbers. """ +from __future__ import annotations + from typing import Any from ...type5.kindexpr import Star from ...type5.typeexpr import TypeVariable -from ...type3.routers import TypeVariableLookup from ...typeclass import TypeClass from ...wasmgenerator import Generator as WasmGenerator from ..base import BuildBase @@ -14,18 +15,15 @@ from ..base import BuildBase def load(build: BuildBase[Any]) -> None: 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_method(Floating, 'sqrt', build.type5_make_function([a, a])) - - # 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? + # FIXME: inherited_classes=[Fractional] + # FIXME: Do we want to expose copysign? def wasm_f32_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None: del tv_map @@ -38,9 +36,9 @@ def wasm_f64_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None: def wasm(build: BuildBase[WasmGenerator]) -> None: 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, }) - build.register_type_class_instance(Floating, build.type5s['f64'], methods={ + build.register_type_class_instance(Floating, build.types['f64'], methods={ 'sqrt': wasm_f64_sqrt, }) diff --git a/phasm/build/typevariablerouter.py b/phasm/build/typevariablerouter.py index 3f65ffd..6d216b5 100644 --- a/phasm/build/typevariablerouter.py +++ b/phasm/build/typevariablerouter.py @@ -1,39 +1,39 @@ -from typing import Any, Callable, Iterable, TypeAlias +from typing import Any, Callable, Iterable from ..type5 import typeexpr as type5typeexpr class TypeVariableRouter[G]: - __slots__ = ('data', ) + __slots__ = ('data', ) - data: dict[ - tuple[tuple[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr], ...], - Callable[[G, dict[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr]], None], - ] + data: dict[ + tuple[tuple[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr], ...], + Callable[[G, dict[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr]], None], + ] - def __init__(self) -> None: - self.data = {} + def __init__(self) -> None: + self.data = {} - def register( - self, - variables: Iterable[type5typeexpr.TypeVariable], - types: Iterable[type5typeexpr.TypeExpr], - implementation: Callable[[G, Any], None], - ) -> None: - variables = list(variables) - types = list(types) + def register( + self, + variables: Iterable[type5typeexpr.TypeVariable], + types: Iterable[type5typeexpr.TypeExpr], + implementation: Callable[[G, Any], None], + ) -> None: + variables = list(variables) + types = list(types) - assert len(variables) == len(set(variables)) + assert len(variables) == len(set(variables)) - key = tuple(sorted(tuple(zip(variables, types)))) - self.data[key] = implementation + key = tuple(sorted(tuple(zip(variables, types)))) + self.data[key] = implementation - def __call__( - self, - variables: Iterable[type5typeexpr.TypeVariable], - types: Iterable[type5typeexpr.TypeExpr], - ) -> Callable[[G, Any], None]: - variables = list(variables) - types = list(types) + def __call__( + self, + variables: Iterable[type5typeexpr.TypeVariable], + types: Iterable[type5typeexpr.TypeExpr], + ) -> Callable[[G, Any], None]: + variables = list(variables) + types = list(types) - key = tuple(sorted(tuple(zip(variables, types)))) - return self.data[key] + key = tuple(sorted(tuple(zip(variables, types)))) + return self.data[key] diff --git a/phasm/compiler.py b/phasm/compiler.py index 2b0b50b..c918847 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -10,13 +10,7 @@ from .build.base import BuildBase, TypeInfo from .build.typerouter import BuildTypeRouter from .stdlib import alloc as stdlib_alloc from .stdlib import types as stdlib_types -from .type3.functions import FunctionArgument, TypeVariable -from .type3.routers import NoRouteForTypeException -from .type3.typeclasses import Type3ClassMethod -from .type3.types import ( - Type3, -) -from .type5.typeexpr import TypeExpr, is_concrete +from .type5.typeexpr import AtomicType, ConstrainedExpr, TypeApplication, TypeExpr, is_concrete from .type5.unify import ActionList, ReplaceVariable, unify from .wasm import ( WasmTypeFloat32, @@ -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) 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.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_info = mod.build.type_info_map.get(el_type.name) 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) 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: for arg in inp.arguments: expression(wgn, mod, arg) @@ -162,7 +159,12 @@ def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerat if isinstance(inp.function_instance.function, ourlang.BuiltinFunction): assert _is_concrete(inp.function_instance.type5), TYPE5_ASSERTION_ERROR - method_type, method_router = mod.build.methods[inp.function_instance.function.name] + try: + 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 actions = unify(method_type, instance_type) @@ -305,10 +307,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl member_type = dict(st_args)[inp.member] member_type_info = mod.build.type_info_map.get(member_type.name) if member_type_info is None: - member_type_info = mod.build.type_info_map['ptr'] - offset = mod.build.type5_struct_offset( - st_args, inp.member, - ) + member_type_info = mod.build.type_info_constructed + offset = _type5_struct_offset(mod.build, st_args, inp.member) expression(wgn, mod, inp.varref) wgn.add_statement(member_type_info.wasm_load_func, 'offset=' + str(offset)) @@ -444,7 +444,7 @@ def module_data(mod: ourlang.Module[WasmGenerator], inp: ourlang.ModuleData) -> """ unalloc_ptr = stdlib_alloc.UNALLOC_PTR 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'' @@ -560,7 +560,7 @@ def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGen for memname, mtyp5 in st_args: mtyp5_info = mod.build.type_info_map.get(mtyp5.name) 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.add_statement('local.get', f'${memname}') @@ -571,8 +571,40 @@ def _generate_struct_constructor(wgn: WasmGenerator, mod: ourlang.Module[WasmGen # Return the allocated address 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: return False + if isinstance(type5, ConstrainedExpr): + type5 = type5.expr + 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 diff --git a/phasm/ourlang.py b/phasm/ourlang.py index c75549e..cebf539 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -6,8 +6,6 @@ from __future__ import annotations from typing import Dict, Iterable, List, Optional, Union 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 typeexpr as type5typeexpr @@ -34,15 +32,13 @@ class Expression: """ An expression within a statement """ - __slots__ = ('type3', 'type5', 'sourceref', ) + __slots__ = ('type5', 'sourceref', ) sourceref: SourceRef - type3: Type3 | None type5: type5typeexpr.TypeExpr | None def __init__(self, *, sourceref: SourceRef) -> None: self.sourceref = sourceref - self.type3 = None self.type5 = None class Constant(Expression): @@ -122,22 +118,19 @@ class ConstantStruct(ConstantMemoryStored): """ 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 value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']] def __init__( self, - struct_type3: Type3, struct_type5: type5record.Record, value: List[Union[ConstantPrimitive, ConstantBytes, ConstantTuple, 'ConstantStruct']], data_block: 'ModuleDataBlock', sourceref: SourceRef ) -> None: super().__init__(data_block, sourceref=sourceref) - self.struct_type3 = struct_type3 self.struct_type5 = struct_type5 self.value = value @@ -145,7 +138,7 @@ class ConstantStruct(ConstantMemoryStored): # Do not repr the whole ModuleDataBlock # As this has a reference back to this constant for its data # 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): """ @@ -253,17 +246,15 @@ class AccessStructMember(Expression): """ Access a struct member for reading of writing """ - __slots__ = ('varref', 'struct_type3', 'member', ) + __slots__ = ('varref', 'member', ) varref: VariableReference - struct_type3: Type3 # FIXME: Remove this (already at varref.type5) 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) self.varref = varref - self.struct_type3 = struct_type3 self.member = member class Statement: @@ -319,68 +310,60 @@ class FunctionParam: """ A parameter for a Function """ - __slots__ = ('name', 'type3', 'type5', ) + __slots__ = ('name', 'type5', ) name: str - type3: Type3 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) self.name = name - self.type3 = type3 self.type5 = type5 def __repr__(self) -> str: - return f'FunctionParam({self.name!r}, {self.type3!r})' + return f'FunctionParam({self.name!r}, {self.type5!r})' class Function: """ 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 sourceref: SourceRef exported: bool imported: Optional[str] statements: List[Statement] - signature: FunctionSignature # TODO: Remove me - returns_type3: Type3 # TODO: Remove me - type5: type5typeexpr.TypeExpr | None + type5: type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr | None posonlyargs: List[FunctionParam] # TODO: Replace me with arg names 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.sourceref = sourceref self.exported = False self.imported = None self.statements = [] - self.signature = FunctionSignature(TypeVariableContext(), []) - self.returns_type3 = returns_type3 self.type5 = None self.posonlyargs = [] self.arg_names = [] class BuiltinFunction(Function): - def __init__(self, name: str, type5: type5typeexpr.TypeExpr) -> None: - super().__init__(name, SourceRef("/", 0, 0), None) + def __init__(self, name: str, type5: type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr) -> None: + super().__init__(name, SourceRef("/", 0, 0)) self.type5 = type5 class StructDefinition: """ The definition for a struct """ - __slots__ = ('struct_type3', 'struct_type5', 'sourceref', ) + __slots__ = ('struct_type5', 'sourceref', ) - struct_type3: Type3 struct_type5: type5record.Record sourceref: SourceRef - def __init__(self, struct_type3: Type3, struct_type5: type5record.Record, sourceref: SourceRef) -> None: - self.struct_type3 = struct_type3 + def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None: self.struct_type5 = struct_type5 self.sourceref = sourceref @@ -391,44 +374,32 @@ class StructConstructor(Function): A function will generated to instantiate a struct. The arguments will be the defaults """ - __slots__ = ('struct_type3', 'struct_type5', ) + __slots__ = ('struct_type5', ) - struct_type3: Type3 struct_type5: type5record.Record - def __init__(self, struct_type3: Type3, struct_type5: type5record.Record, sourceref: SourceRef) -> None: - super().__init__(f'@{struct_type3.name}@__init___@', sourceref, struct_type3) - - 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 + def __init__(self, struct_type5: type5record.Record, sourceref: SourceRef) -> None: + super().__init__(f'@{struct_type5.name}@__init___@', sourceref) 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: """ A constant definition within a module """ - __slots__ = ('name', 'sourceref', 'type3', 'type5', 'constant', ) + __slots__ = ('name', 'sourceref', 'type5', 'constant', ) name: str sourceref: SourceRef - type3: Type3 type5: type5typeexpr.TypeExpr 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.sourceref = sourceref - self.type3 = type3 self.type5 = type5 self.constant = constant @@ -468,13 +439,12 @@ class Module[G]: build: BuildBase[G] filename: str data: ModuleData - types: dict[str, Type3] - type5s: dict[str, type5typeexpr.TypeExpr] + types: dict[str, type5typeexpr.TypeExpr] struct_definitions: Dict[str, StructDefinition] constant_defs: Dict[str, ModuleConstantDef] functions: Dict[str, Function] - methods: Dict[str, type5typeexpr.TypeExpr] - operators: Dict[str, type5typeexpr.TypeExpr] + methods: Dict[str, type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr] + operators: Dict[str, type5typeexpr.TypeExpr | type5typeexpr.ConstrainedExpr] functions_table: dict[Function, int] def __init__(self, build: BuildBase[G], filename: str) -> None: @@ -483,7 +453,6 @@ class Module[G]: self.data = ModuleData() self.types = {} - self.type5s = {} self.struct_definitions = {} self.constant_defs = {} self.functions = {} diff --git a/phasm/parser.py b/phasm/parser.py index 65ba53e..8827117 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -35,8 +35,6 @@ from .ourlang import ( TupleInstantiation, VariableReference, ) -from .type3.typeclasses import Type3ClassMethod -from .type3.types import IntType3, Type3 from .type5 import typeexpr as type5typeexpr from .wasmgenerator import Generator @@ -103,9 +101,8 @@ class OurVisitor[G]: module = Module(self.build, "-") 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.type5s.update(self.build.type5s) _not_implemented(not node.type_ignores, 'Module.type_ignores') @@ -123,17 +120,16 @@ class OurVisitor[G]: module.constant_defs[res.name] = res if isinstance(res, StructDefinition): - if res.struct_type3.name in module.types: + if res.struct_type5.name in module.types: 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.type5s[res.struct_type5.name] = res.struct_type5 - module.functions[res.struct_type3.name] = StructConstructor(res.struct_type3, res.struct_type5, res.sourceref) - module.functions[res.struct_type3.name].type5 = module.build.type5_make_function([x[1] for x in res.struct_type5.fields] + [res.struct_type5]) + module.types[res.struct_type5.name] = res.struct_type5 + module.functions[res.struct_type5.name] = StructConstructor(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]) # 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 res.name in module.functions: @@ -163,7 +159,7 @@ class OurVisitor[G]: raise NotImplementedError(f'{node} on Module') def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function: - function = Function(node.name, srf(module, node), self.build.none_) + function = Function(node.name, srf(module, node)) _not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs') @@ -173,7 +169,6 @@ class OurVisitor[G]: if arg.annotation is None: _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_list.append(arg_type5) @@ -182,10 +177,8 @@ class OurVisitor[G]: # This would also require FunctionParam to accept a placeholder function.arg_names.append(arg.arg) - function.signature.args.append(arg_type) function.posonlyargs.append(FunctionParam( arg.arg, - arg_type, arg_type5, )) @@ -229,12 +222,8 @@ class OurVisitor[G]: if node.returns is None: # Note: `-> None` would be a ast.Constant _raise_static_error(node, 'Must give a return type') - return_type = self.visit_type(module, node.returns) 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) _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.decorator_list, 'ClassDef.decorator_list') - members: Dict[str, Type3] = {} - members5: Dict[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication] = {} + members: Dict[str, type5typeexpr.AtomicType | type5typeexpr.TypeApplication] = {} for stmt in node.body: if not isinstance(stmt, ast.AnnAssign): @@ -266,15 +254,12 @@ class OurVisitor[G]: if stmt.target.id in members: _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) assert isinstance(field_type5, (type5typeexpr.AtomicType, type5typeexpr.TypeApplication, )) - members5[stmt.target.id] = field_type5 + members[stmt.target.id] = field_type5 return StructDefinition( - module.build.struct(node.name, tuple(members.items())), - module.build.type5_make_struct(node.name, tuple(members5.items())), + module.build.type5_make_struct(node.name, tuple(members.items())), srf(module, node), ) @@ -290,7 +275,6 @@ class OurVisitor[G]: return ModuleConstantDef( node.target.id, srf(module, node), - self.visit_type(module, node.annotation), self.visit_type5(module, node.annotation), value_data, ) @@ -304,7 +288,6 @@ class OurVisitor[G]: return ModuleConstantDef( node.target.id, srf(module, node), - self.visit_type(module, node.annotation), self.visit_type5(module, node.annotation), value_data, ) @@ -318,7 +301,6 @@ class OurVisitor[G]: return ModuleConstantDef( node.target.id, srf(module, node), - self.visit_type(module, node.annotation), self.visit_type5(module, node.annotation), 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: if isinstance(node, ast.BinOp): - operator: Union[str, Type3ClassMethod] + operator: str if isinstance(node.op, ast.Add): operator = '+' @@ -417,7 +399,7 @@ class OurVisitor[G]: raise NotImplementedError(f'Operator {operator}') 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.right), srf(module, node), @@ -446,7 +428,7 @@ class OurVisitor[G]: raise NotImplementedError(f'Operator {operator}') 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.comparators[0]), srf(module, node), @@ -511,7 +493,7 @@ class OurVisitor[G]: if not isinstance(node.func.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - func: Union[Function, FunctionParam, Type3ClassMethod] + func: Union[Function, FunctionParam] if node.func.id in module.methods: func = BuiltinFunction(node.func.id, module.methods[node.func.id]) @@ -543,7 +525,6 @@ class OurVisitor[G]: return AccessStructMember( varref, - varref.variable.type3, node.attr, srf(module, node), ) @@ -617,7 +598,7 @@ class OurVisitor[G]: data_block = ModuleDataBlock(struct_data) 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') @@ -634,70 +615,6 @@ class OurVisitor[G]: 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: if isinstance(node, ast.Constant): if node.value is None: @@ -709,8 +626,8 @@ class OurVisitor[G]: if not isinstance(node.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - if node.id in module.type5s: - return module.type5s[node.id] + if node.id in module.types: + return module.types[node.id] _raise_static_error(node, f'Unrecognized type {node.id}') diff --git a/phasm/type3/__init__.py b/phasm/type3/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py deleted file mode 100644 index b8c71c4..0000000 --- a/phasm/type3/constraints.py +++ /dev/null @@ -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)})' diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py deleted file mode 100644 index a666885..0000000 --- a/phasm/type3/constraintsgenerator.py +++ /dev/null @@ -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) diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py deleted file mode 100644 index 9b7f96a..0000000 --- a/phasm/type3/entry.py +++ /dev/null @@ -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 ^ === ') diff --git a/phasm/type3/functions.py b/phasm/type3/functions.py deleted file mode 100644 index d10e2b1..0000000 --- a/phasm/type3/functions.py +++ /dev/null @@ -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})' diff --git a/phasm/type3/placeholders.py b/phasm/type3/placeholders.py deleted file mode 100644 index 58f8467..0000000 --- a/phasm/type3/placeholders.py +++ /dev/null @@ -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] diff --git a/phasm/type3/routers.py b/phasm/type3/routers.py deleted file mode 100644 index 26fa6cc..0000000 --- a/phasm/type3/routers.py +++ /dev/null @@ -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) diff --git a/phasm/type3/typeclasses.py b/phasm/type3/typeclasses.py deleted file mode 100644 index 02c6ad0..0000000 --- a/phasm/type3/typeclasses.py +++ /dev/null @@ -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) diff --git a/phasm/type3/types.py b/phasm/type3/types.py deleted file mode 100644 index 9406c1b..0000000 --- a/phasm/type3/types.py +++ /dev/null @@ -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 diff --git a/phasm/type5/__main__.py b/phasm/type5/__main__.py deleted file mode 100644 index 019cede..0000000 --- a/phasm/type5/__main__.py +++ /dev/null @@ -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() diff --git a/phasm/type5/constraints.py b/phasm/type5/constraints.py index d0f80ca..3f9648b 100644 --- a/phasm/type5/constraints.py +++ b/phasm/type5/constraints.py @@ -5,12 +5,10 @@ from typing import Any, Callable, Iterable, Protocol, Sequence from ..build.base import BuildBase from ..ourlang import SourceRef -from ..type3 import types as type3types from ..wasm import WasmTypeFloat32, WasmTypeFloat64, WasmTypeInt32, WasmTypeInt64 from .kindexpr import KindExpr, Star from .record import Record from .typeexpr import ( - AtomicType, TypeExpr, TypeVariable, is_concrete, @@ -440,6 +438,10 @@ class TypeClassInstanceExistsConstraint(ConstraintBase): if len(c_arg_list) != len(self.arg_list): 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) existing_instances = self.ctx.build.type_class_instances[self.typeclass] if key in existing_instances: @@ -456,27 +458,3 @@ class TypeClassInstanceExistsConstraint(ConstraintBase): def __str__(self) -> str: args = ' '.join(self.ctx.build.type5_name(x) for x in self.arg_list) 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) diff --git a/phasm/type5/fromast.py b/phasm/type5/fromast.py index 8d0d8a4..3387253 100644 --- a/phasm/type5/fromast.py +++ b/phasm/type5/fromast.py @@ -1,9 +1,6 @@ from typing import Any, Generator from .. import ourlang -from ..type3 import functions as type3functions -from ..type3 import typeclasses as type3classes -from ..type3 import types as type3types from .constraints import ( CanAccessStructMemberConstraint, CanBeSubscriptedConstraint, @@ -16,8 +13,8 @@ from .constraints import ( TypeClassInstanceExistsConstraint, UnifyTypesConstraint, ) -from .kindexpr import Star -from .typeexpr import TypeApplication, TypeExpr, TypeVariable, instantiate +from .typeexpr import ConstrainedExpr, TypeApplication, TypeExpr, TypeVariable, instantiate, instantiate_constrained +from ..typeclass import TypeClassConstraint 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) def expression_binary_operator(ctx: Context, inp: ourlang.BinaryOp, phft: TypeVariable) -> ConstraintGenerator: - yield from expression_function_call( - ctx, - _binary_op_to_function(ctx, inp), - phft, - ) + yield from expression_function_call(ctx, _binary_op_to_function(inp), phft) def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: TypeVariable) -> ConstraintGenerator: 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) arg_typ_list.append(arg_tv) - assert isinstance(inp.function_instance.function.type5, TypeExpr) - 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) - # constraints = [] + make_placeholder = lambda x, p: ctx.make_placeholder(kind=x, prefix=p) + + ftp5 = inp.function_instance.function.type5 + 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]) - yield UnifyTypesConstraint(ctx, inp.sourceref, inp.function_instance.type5, expr_type) - # yield from constraints + yield UnifyTypesConstraint(ctx, inp.sourceref, phft2, expr_type) def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: TypeVariable) -> ConstraintGenerator: 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: arg_typ_list = [] @@ -196,7 +213,7 @@ def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement assert args is not None type5 = args[-1] else: - type5 = fun.type5 + type5 = fun.type5.expr if isinstance(fun.type5, ConstrainedExpr) else fun.type5 yield from expression(ctx, inp.value, phft) yield UnifyTypesConstraint(ctx, inp.sourceref, type5, phft) @@ -247,99 +264,13 @@ def module(ctx: Context, inp: ourlang.Module[Any]) -> ConstraintGenerator: # 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 call = ourlang.FunctionCall(inp.operator, inp.sourceref) call.arguments = [inp.left, inp.right] 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, ) diff --git a/phasm/type5/typeexpr.py b/phasm/type5/typeexpr.py index 33b24ae..a2f030e 100644 --- a/phasm/type5/typeexpr.py +++ b/phasm/type5/typeexpr.py @@ -1,7 +1,7 @@ from __future__ import annotations -from dataclasses import dataclass -from typing import Callable, Sequence +from dataclasses import dataclass, field +from typing import Callable, Iterable, Self, Sequence from .kindexpr import Arrow, KindExpr, Nat, Star @@ -38,8 +38,6 @@ class TypeVariable(TypeExpr): """ A placeholder in a type expression """ - constraints: Sequence[Callable[[TypeExpr], bool]] = () - def __hash__(self) -> int: return hash((self.kind, self.name)) @@ -53,7 +51,11 @@ class TypeApplication(TypeExpr): constructor: TypeConstructor | TypeApplication | TypeVariable 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): raise TypeError("A constructor cannot be a concrete type") @@ -71,6 +73,23 @@ class TypeApplication(TypeExpr): self.constructor = constructor 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: """ Checks whether the given variable occurs in the given application. @@ -152,6 +171,14 @@ def instantiate( known_map: dict[TypeVariable, TypeVariable], make_variable: Callable[[KindExpr, str], TypeVariable], ) -> 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): return expr @@ -176,3 +203,18 @@ def instantiate( ) 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) diff --git a/phasm/typeclass/__init__.py b/phasm/typeclass/__init__.py index 53dcae9..1ab3b80 100644 --- a/phasm/typeclass/__init__.py +++ b/phasm/typeclass/__init__.py @@ -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: - name: str - variables: list[TypeVariable] + name: str + 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 + ] + ) diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index c6d6438..37df219 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -1,13 +1,13 @@ +from __future__ import annotations + import os import struct import sys from typing import Any, Callable, Generator, Iterable, List, TextIO, Union from phasm import compiler -from phasm.build import builtins from phasm.codestyle import phasm_render -from phasm.type3 import types as type3types -from phasm.type3.routers import NoRouteForTypeException, TypeApplicationRouter +from phasm.type5.typeexpr import TypeExpr from phasm.wasm import ( WasmTypeFloat32, WasmTypeFloat64, @@ -17,6 +17,7 @@ from phasm.wasm import ( ) from . import runners +from . import memory DASHES = '-' * 16 @@ -115,7 +116,10 @@ class Suite: if do_format_check: assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests - func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs] + 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): raise RuntimeError(f'Invalid number of args for {func_name}') @@ -151,11 +155,8 @@ class Suite: result = SuiteResult() result.returned_value = runner.call(func_name, *wasm_args) - result.returned_value = _load_memory_stored_returned_value( - runner, - func_name, - result.returned_value, - ) + loader = memory.Loader(runner.phasm_ast.build, runner)(func_ret) + result.returned_value = loader(result.returned_value) if verbose: write_header(sys.stderr, 'Memory (post run)') @@ -169,7 +170,7 @@ def write_header(textio: TextIO, msg: str) -> None: def _write_memory_stored_value( runner: runners.RunnerBase, adr: int, - val_typ: type3types.Type3, + val_typ: TypeExpr, val: Any, ) -> int: try: @@ -197,7 +198,7 @@ def _allocate_memory_stored_bytes(attrs: tuple[runners.RunnerBase, bytes]) -> in runner.interpreter_write_memory(adr + 4, val) 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 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) 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 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) 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 assert isinstance(val, dict) @@ -259,7 +260,7 @@ def _allocate_memory_stored_struct(attrs: tuple[runners.RunnerBase, Any], st_arg 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 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) 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( runner: runners.RunnerBase, func_name: str, @@ -316,7 +311,7 @@ def _load_memory_stored_returned_value( 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) 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] 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 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) ) -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 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) ) -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 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) } -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 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) 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) diff --git a/tests/integration/memory.py b/tests/integration/memory.py new file mode 100644 index 0000000..328e58a --- /dev/null +++ b/tests/integration/memory.py @@ -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(' 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('