diff --git a/phasm/build/base.py b/phasm/build/base.py index f8d3687..fb49486 100644 --- a/phasm/build/base.py +++ b/phasm/build/base.py @@ -16,7 +16,7 @@ from ..type3.routers import ( TypeClassArgsRouter, TypeVariableLookup, ) -from ..type3.typeclasses import Type3Class, Type3ClassMethod +from ..typeclass import TypeClass from ..type3.types import ( IntType3, Type3, @@ -33,6 +33,7 @@ from ..type5 import typeexpr as type5typeexpr from ..wasm import WasmType, WasmTypeInt32, WasmTypeNone from . import builtins from .typerouter import TypeAllocSize, TypeName +from .typevariablerouter import TypeVariableRouter TypeInfo = NamedTuple('TypeInfo', [ # Name of the type @@ -179,27 +180,27 @@ class BuildBase[G]: Types that are available without explicit import. """ - type_classes: dict[str, Type3Class] + type_classes: dict[str, TypeClass] """ Type classes that are available without explicit import. """ - type_class_instances: set[tuple[Type3Class, tuple[Type3 | TypeConstructor_Base[Any], ...]]] + type_class_instances: dict[str, set[tuple[type5typeexpr.TypeExpr, ...]]] """ Type class instances that are available without explicit import. """ - type_class_instance_methods: dict[Type3ClassMethod, TypeClassArgsRouter[G, None]] + # 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, Type3ClassMethod] + methods: dict[str, tuple[type5typeexpr.TypeExpr, TypeVariableRouter[G]]] """ Methods that are available without explicit import. """ - operators: dict[str, Type3ClassMethod] + # operators: dict[str, Type3ClassMethod] """ Operators that are available without explicit import. """ @@ -268,7 +269,7 @@ class BuildBase[G]: 'u32': self.u32_type5, } self.type_classes = {} - self.type_class_instances = set() + self.type_class_instances = {} self.type_class_instance_methods = {} self.methods = {} self.operators = {} @@ -282,83 +283,112 @@ class BuildBase[G]: 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) + # 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.methods.update(cls.methods) - self.operators.update(cls.operators) + self.type_class_instances[cls.name] = set() - 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 register_type_class_method( + self, + cls: TypeClass, + name: str, + type: type5typeexpr.TypeExpr, + ) -> None: + assert name not in self.methods, 'Duplicate typeclass method name' - 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) + self.methods[name] = (type, TypeVariableRouter(), ) - 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 - )) + def register_type_class_instance( + self, + cls: TypeClass, + *args: type5typeexpr.TypeExpr, + methods: dict[str, Callable[[G, Any], None]], + ) -> None: + self.type_class_instances[cls.name].add(tuple(args)) - # 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) + assert len(cls.variables) == len(args) + for mtd_nam, mtd_imp in methods.items(): + _, 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: """ diff --git a/phasm/build/default.py b/phasm/build/default.py index c072457..6a5cc70 100644 --- a/phasm/build/default.py +++ b/phasm/build/default.py @@ -98,14 +98,15 @@ class BuildDefault(BuildBase[Generator]): }) tc_list = [ - bits, - eq, ord, - extendable, promotable, - convertable, reinterpretable, - natnum, intnum, fractional, floating, - integral, - foldable, subscriptable, - sized, + floating, + # bits, + # eq, ord, + # extendable, promotable, + # convertable, reinterpretable, + # natnum, intnum, fractional, floating, + # integral, + # foldable, subscriptable, + # sized, ] for tc in tc_list: diff --git a/phasm/build/typeclasses/floating.py b/phasm/build/typeclasses/floating.py index dc838e8..10ce0ac 100644 --- a/phasm/build/typeclasses/floating.py +++ b/phasm/build/typeclasses/floating.py @@ -3,24 +3,30 @@ The Floating type class is defined for Real numbers. """ from typing import Any -from ...type3.functions import make_typevar +from ...type5.kindexpr import Star +from ...type5.typeexpr import TypeVariable from ...type3.routers import TypeVariableLookup -from ...type3.typeclasses import Type3Class +from ...typeclass import TypeClass from ...wasmgenerator import Generator as WasmGenerator from ..base import BuildBase def load(build: BuildBase[Any]) -> None: - a = make_typevar('a') - Fractional = build.type_classes['Fractional'] - - Floating = Type3Class('Floating', (a, ), methods={ - 'sqrt': [a, a], - }, operators={}, inherited_classes=[Fractional]) - # FIXME: Do we want to expose copysign? + a = TypeVariable(kind=Star(), name='a') + Floating = TypeClass('Floating', [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? + def wasm_f32_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None: del tv_map g.add_statement('f32.sqrt') @@ -32,9 +38,9 @@ def wasm_f64_sqrt(g: WasmGenerator, tv_map: TypeVariableLookup) -> None: def wasm(build: BuildBase[WasmGenerator]) -> None: Floating = build.type_classes['Floating'] - build.instance_type_class(Floating, build.types['f32'], methods={ + build.register_type_class_instance(Floating, build.type5s['f32'], methods={ 'sqrt': wasm_f32_sqrt, }) - build.instance_type_class(Floating, build.types['f64'], methods={ + build.register_type_class_instance(Floating, build.type5s['f64'], methods={ 'sqrt': wasm_f64_sqrt, }) diff --git a/phasm/build/typevariablerouter.py b/phasm/build/typevariablerouter.py new file mode 100644 index 0000000..3f65ffd --- /dev/null +++ b/phasm/build/typevariablerouter.py @@ -0,0 +1,39 @@ +from typing import Any, Callable, Iterable, TypeAlias + +from ..type5 import typeexpr as type5typeexpr + +class TypeVariableRouter[G]: + __slots__ = ('data', ) + + data: dict[ + tuple[tuple[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr], ...], + Callable[[G, dict[type5typeexpr.TypeVariable, type5typeexpr.TypeExpr]], None], + ] + + 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) + + assert len(variables) == len(set(variables)) + + 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) + + key = tuple(sorted(tuple(zip(variables, types)))) + return self.data[key] diff --git a/phasm/codestyle.py b/phasm/codestyle.py index fc0f8ef..965b345 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -6,7 +6,6 @@ It's intented to be a "any color, as long as it's black" kind of renderer from typing import Any, Generator from . import ourlang -from .type3.types import Type3, TypeApplication_Struct from .type5 import typeexpr as type5typeexpr @@ -18,35 +17,27 @@ def phasm_render(inp: ourlang.Module[Any]) -> str: Statements = Generator[str, None, None] -def type3(inp: Type3) -> str: - """ - Render: type's name - """ - return inp.name - def type5(mod: ourlang.Module[Any], inp: type5typeexpr.TypeExpr) -> str: """ Render: type's name """ return mod.build.type5_name(inp) -def struct_definition(inp: ourlang.StructDefinition) -> str: +def struct_definition(mod: ourlang.Module[Any], inp: ourlang.StructDefinition) -> str: """ Render: TypeStruct's definition """ - assert isinstance(inp.struct_type3.application, TypeApplication_Struct) - - result = f'class {inp.struct_type3.name}:\n' - for mem, typ in inp.struct_type3.application.arguments: - result += f' {mem}: {type3(typ)}\n' + result = f'class {inp.struct_type5.name}:\n' + for mem, typ in inp.struct_type5.fields: + result += f' {mem}: {type5(mod, typ)}\n' return result -def constant_definition(inp: ourlang.ModuleConstantDef) -> str: +def constant_definition(mod: ourlang.Module[Any], inp: ourlang.ModuleConstantDef) -> str: """ Render: Module Constant's definition """ - return f'{inp.name}: {type3(inp.type3)} = {expression(inp.constant)}\n' + return f'{inp.name}: {type5(mod, inp.type5)} = {expression(inp.constant)}\n' def expression(inp: ourlang.Expression) -> str: """ @@ -67,7 +58,7 @@ def expression(inp: ourlang.Expression) -> str: ) + ', )' if isinstance(inp, ourlang.ConstantStruct): - return inp.struct_type3.name + '(' + ', '.join( + return inp.struct_type5.name + '(' + ', '.join( expression(x) for x in inp.value ) + ')' @@ -76,7 +67,7 @@ def expression(inp: ourlang.Expression) -> str: return str(inp.variable.name) if isinstance(inp, ourlang.BinaryOp): - return f'{expression(inp.left)} {inp.operator.name} {expression(inp.right)}' + return f'{expression(inp.left)} {inp.operator.function.name} {expression(inp.right)}' if isinstance(inp, ourlang.FunctionCall): args = ', '.join( @@ -84,10 +75,10 @@ def expression(inp: ourlang.Expression) -> str: for arg in inp.arguments ) - if isinstance(inp.function, ourlang.StructConstructor): - return f'{inp.function.struct_type3.name}({args})' + if isinstance(inp.function_instance.function, ourlang.StructConstructor): + return f'{inp.function_instance.function.struct_type5.name}({args})' - return f'{inp.function.name}({args})' + return f'{inp.function_instance.function.name}({args})' if isinstance(inp, ourlang.FunctionReference): return str(inp.function.name) @@ -148,12 +139,17 @@ def function(mod: ourlang.Module[Any], inp: ourlang.Function) -> str: if inp.imported: result += '@imported\n' + assert inp.type5 is not None + fn_args = mod.build.type5_is_function(inp.type5) + assert fn_args is not None + ret_type5 = fn_args.pop() + args = ', '.join( - f'{p.name}: {type5(mod, p.type5)}' - for p in inp.posonlyargs + f'{arg_name}: {type5(mod, arg_type)}' + for arg_name, arg_type in zip(inp.arg_names, fn_args, strict=True) ) - result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n' + result += f'def {inp.name}({args}) -> {type5(mod, ret_type5)}:\n' if inp.imported: result += ' pass\n' @@ -174,12 +170,12 @@ def module(inp: ourlang.Module[Any]) -> str: for struct in inp.struct_definitions.values(): if result: result += '\n' - result += struct_definition(struct) + result += struct_definition(inp, struct) for cdef in inp.constant_defs.values(): if result: result += '\n' - result += constant_definition(cdef) + result += constant_definition(inp, cdef) for func in inp.functions.values(): if isinstance(func, ourlang.StructConstructor): diff --git a/phasm/compiler.py b/phasm/compiler.py index 394c9e8..2b0b50b 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -15,10 +15,9 @@ from .type3.routers import NoRouteForTypeException from .type3.typeclasses import Type3ClassMethod from .type3.types import ( Type3, - TypeApplication_Struct, - TypeConstructor_Function, ) from .type5.typeexpr import TypeExpr, is_concrete +from .type5.unify import ActionList, ReplaceVariable, unify from .wasm import ( WasmTypeFloat32, WasmTypeFloat64, @@ -156,6 +155,48 @@ def expression_subscript_tuple(wgn: WasmGenerator, mod: ourlang.Module[WasmGener expression(wgn, mod, inp.varref) wgn.add_statement(el_type_info.wasm_load_func, f'offset={offset}') +def expression_function_call(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.FunctionCall) -> None: + for arg in inp.arguments: + expression(wgn, mod, arg) + + 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] + instance_type = inp.function_instance.type5 + + actions = unify(method_type, instance_type) + assert isinstance(actions, ActionList) + + tv_map = {} + for action in actions: + if isinstance(action, ReplaceVariable): + tv_map[action.var] = action.typ + continue + raise NotImplementedError + + method_router(tv_map.keys(), tv_map.values())(wgn, None) + return + + if isinstance(inp.function_instance.function, ourlang.FunctionParam): + assert _is_concrete(inp.function_instance.type5), TYPE5_ASSERTION_ERROR + + fn_args = mod.build.type5_is_function(inp.function_instance.type5) + assert fn_args is not None + + params = [ + type5(mod, x) + for x in fn_args + ] + + result = params.pop() + + wgn.add_statement('local.get', '${}'.format(inp.function_instance.function.name)) + wgn.call_indirect(params=params, result=result) + return + + wgn.call(inp.function_instance.function.name) + def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourlang.Expression) -> None: """ Compile: Any expression @@ -217,80 +258,11 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl raise NotImplementedError(expression, inp.variable) if isinstance(inp, ourlang.BinaryOp): - expression(wgn, mod, inp.left) - expression(wgn, mod, inp.right) - - type_var_map: dict[TypeVariable, Type3] = {} - - for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True): - assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR - - if isinstance(type_var, Type3): - # Fixed type, not part of the lookup requirements - continue - - if isinstance(type_var, TypeVariable): - type_var_map[type_var] = arg_expr.type3 - continue - - if isinstance(type_var, FunctionArgument): - # Fixed type, not part of the lookup requirements - continue - - raise NotImplementedError(type_var, arg_expr.type3) - - router = mod.build.type_class_instance_methods[inp.operator] - router(wgn, type_var_map) + expression_binary_op(wgn, mod, inp) return if isinstance(inp, ourlang.FunctionCall): - for arg in inp.arguments: - expression(wgn, mod, arg) - - if isinstance(inp.function, Type3ClassMethod): - # FIXME: Duplicate code with BinaryOp - type_var_map = {} - - for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True): - assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR - - if isinstance(type_var, Type3): - # Fixed type, not part of the lookup requirements - continue - - if isinstance(type_var, TypeVariable): - type_var_map[type_var] = arg_expr.type3 - continue - - if isinstance(type_var, FunctionArgument): - # Fixed type, not part of the lookup requirements - continue - - raise NotImplementedError(type_var, arg_expr.type3) - - router = mod.build.type_class_instance_methods[inp.function] - try: - router(wgn, type_var_map) - except NoRouteForTypeException: - raise NotImplementedError(str(inp.function), type_var_map) - return - - if isinstance(inp.function, ourlang.FunctionParam): - fn_args = mod.build.type5_is_function(inp.function.type5) - assert fn_args is not None - - params = [ - type5(mod, x) - for x in fn_args - ] - - result = params.pop() - - wgn.add_statement('local.get', '${}'.format(inp.function.name)) - wgn.call_indirect(params=params, result=result) - return - - wgn.call(inp.function.name) + expression_function_call(wgn, mod, inp) return if isinstance(inp, ourlang.FunctionReference): @@ -314,6 +286,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl expression_subscript_tuple(wgn, mod, inp) return + raise NotImplementedError + inp_as_fc = ourlang.FunctionCall(mod.build.type_classes['Subscriptable'].operators['[]'], inp.sourceref) inp_as_fc.type3 = inp.type3 inp_as_fc.type5 = inp.type5 @@ -349,11 +323,11 @@ def statement_return(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun # Support tail calls # https://github.com/WebAssembly/tail-call # These help a lot with some functional programming techniques - if isinstance(inp.value, ourlang.FunctionCall) and inp.value.function is fun: + if isinstance(inp.value, ourlang.FunctionCall) and inp.value.function_instance.function is fun: for arg in inp.value.arguments: expression(wgn, mod, arg) - wgn.add_statement('return_call', '${}'.format(inp.value.function.name)) + wgn.add_statement('return_call', '${}'.format(inp.value.function_instance.function.name)) return expression(wgn, mod, inp.value) diff --git a/phasm/ourlang.py b/phasm/ourlang.py index ebe8445..c75549e 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -1,11 +1,12 @@ """ Contains the syntax tree for ourlang """ +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.typeclasses import Type3ClassMethod from .type3.types import Type3, TypeApplication_Struct from .type5 import record as type5record from .type5 import typeexpr as type5typeexpr @@ -164,11 +165,11 @@ class BinaryOp(Expression): """ __slots__ = ('operator', 'left', 'right', ) - operator: Type3ClassMethod + operator: FunctionInstance left: Expression right: Expression - def __init__(self, operator: Type3ClassMethod, left: Expression, right: Expression, sourceref: SourceRef) -> None: + def __init__(self, operator: FunctionInstance, left: Expression, right: Expression, sourceref: SourceRef) -> None: super().__init__(sourceref=sourceref) self.operator = operator @@ -178,19 +179,33 @@ class BinaryOp(Expression): def __repr__(self) -> str: return f'BinaryOp({repr(self.operator)}, {repr(self.left)}, {repr(self.right)})' +class FunctionInstance(Expression): + """ + When calling a polymorphic function with concrete arguments, we can generate + code for that specific instance of the function. + """ + __slots__ = ('function', ) + + function: Union['Function', 'FunctionParam'] + + def __init__(self, function: Union['Function', 'FunctionParam'], sourceref: SourceRef) -> None: + super().__init__(sourceref=sourceref) + + self.function = function + class FunctionCall(Expression): """ A function call expression within a statement """ - __slots__ = ('function', 'arguments', ) + __slots__ = ('function_instance', 'arguments', ) - function: Union['Function', 'FunctionParam', Type3ClassMethod] + function_instance: FunctionInstance arguments: List[Expression] - def __init__(self, function: Union['Function', 'FunctionParam', Type3ClassMethod], sourceref: SourceRef) -> None: + def __init__(self, function_instance: FunctionInstance, sourceref: SourceRef) -> None: super().__init__(sourceref=sourceref) - self.function = function + self.function_instance = function_instance self.arguments = [] class FunctionReference(Expression): @@ -349,6 +364,11 @@ class Function: self.posonlyargs = [] self.arg_names = [] +class BuiltinFunction(Function): + def __init__(self, name: str, type5: type5typeexpr.TypeExpr) -> None: + super().__init__(name, SourceRef("/", 0, 0), None) + self.type5 = type5 + class StructDefinition: """ The definition for a struct @@ -453,8 +473,8 @@ class Module[G]: struct_definitions: Dict[str, StructDefinition] constant_defs: Dict[str, ModuleConstantDef] functions: Dict[str, Function] - methods: Dict[str, Type3ClassMethod] - operators: Dict[str, Type3ClassMethod] + methods: Dict[str, type5typeexpr.TypeExpr] + operators: Dict[str, type5typeexpr.TypeExpr] functions_table: dict[Function, int] def __init__(self, build: BuildBase[G], filename: str) -> None: diff --git a/phasm/parser.py b/phasm/parser.py index 3705c85..65ba53e 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -10,6 +10,7 @@ from .exceptions import StaticError from .ourlang import ( AccessStructMember, BinaryOp, + BuiltinFunction, ConstantBytes, ConstantPrimitive, ConstantStruct, @@ -17,6 +18,7 @@ from .ourlang import ( Expression, Function, FunctionCall, + FunctionInstance, FunctionParam, FunctionReference, Module, @@ -100,7 +102,7 @@ class OurVisitor[G]: def visit_Module(self, node: ast.Module) -> Module[G]: module = Module(self.build, "-") - module.methods.update(self.build.methods) + module.methods.update({k: v[0] for k, v in self.build.methods.items()}) module.operators.update(self.build.operators) module.types.update(self.build.types) module.type5s.update(self.build.type5s) @@ -415,7 +417,7 @@ class OurVisitor[G]: raise NotImplementedError(f'Operator {operator}') return BinaryOp( - module.operators[operator], + FunctionInstance(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), @@ -444,7 +446,7 @@ class OurVisitor[G]: raise NotImplementedError(f'Operator {operator}') return BinaryOp( - module.operators[operator], + FunctionInstance(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), @@ -512,7 +514,7 @@ class OurVisitor[G]: func: Union[Function, FunctionParam, Type3ClassMethod] if node.func.id in module.methods: - func = module.methods[node.func.id] + func = BuiltinFunction(node.func.id, module.methods[node.func.id]) elif node.func.id in our_locals: func = our_locals[node.func.id] else: @@ -521,7 +523,7 @@ class OurVisitor[G]: func = module.functions[node.func.id] - result = FunctionCall(func, sourceref=srf(module, node)) + result = FunctionCall(FunctionInstance(func, srf(module, node)), sourceref=srf(module, node)) result.arguments.extend( self.visit_Module_FunctionDef_expr(module, function, our_locals, arg_expr) for arg_expr in node.args diff --git a/phasm/type5/constraints.py b/phasm/type5/constraints.py index 3d88365..d0f80ca 100644 --- a/phasm/type5/constraints.py +++ b/phasm/type5/constraints.py @@ -39,8 +39,8 @@ class Context: self.build = build self.placeholder_update = {} - def make_placeholder(self, arg: ExpressionProtocol | None = None, kind: KindExpr = Star()) -> TypeVariable: - res = TypeVariable(kind, f"p_{len(self.placeholder_update)}") + def make_placeholder(self, arg: ExpressionProtocol | None = None, kind: KindExpr = Star(), prefix: str = 'p') -> TypeVariable: + res = TypeVariable(kind, f"{prefix}_{len(self.placeholder_update)}") self.placeholder_update[res] = arg return res @@ -440,18 +440,9 @@ class TypeClassInstanceExistsConstraint(ConstraintBase): if len(c_arg_list) != len(self.arg_list): return skip_for_now() - tcls = self.ctx.build.type_classes[self.typeclass] - - # Temporary hack while we are converting from type3 to type5 - try: - targs = tuple( - _type5_to_type3_or_type3_const(self.ctx.build, x) - for x in self.arg_list - ) - except RecordFoundException: - return fail('Missing type class instance') - - if (tcls, targs, ) in self.ctx.build.type_class_instances: + key = tuple(c_arg_list) + existing_instances = self.ctx.build.type_class_instances[self.typeclass] + if key in existing_instances: return ok() return fail('Missing type class instance') diff --git a/phasm/type5/fromast.py b/phasm/type5/fromast.py index 20654ca..8d0d8a4 100644 --- a/phasm/type5/fromast.py +++ b/phasm/type5/fromast.py @@ -17,7 +17,7 @@ from .constraints import ( UnifyTypesConstraint, ) from .kindexpr import Star -from .typeexpr import TypeApplication, TypeExpr, TypeVariable +from .typeexpr import TypeApplication, TypeExpr, TypeVariable, instantiate ConstraintGenerator = Generator[ConstraintBase, None, None] @@ -107,17 +107,15 @@ def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: Type yield from expression(ctx, arg, arg_tv) arg_typ_list.append(arg_tv) - if isinstance(inp.function, type3classes.Type3ClassMethod): - func_type, constraints = _signature_to_type5(ctx, inp.sourceref, inp.function.signature) - else: - assert isinstance(inp.function.type5, TypeExpr) - func_type = inp.function.type5 - constraints = [] + 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 = [] expr_type = ctx.build.type5_make_function(arg_typ_list + [phft]) - yield UnifyTypesConstraint(ctx, inp.sourceref, func_type, expr_type) - yield from constraints + yield UnifyTypesConstraint(ctx, inp.sourceref, inp.function_instance.type5, expr_type) + # yield from constraints def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: TypeVariable) -> ConstraintGenerator: assert inp.function.type5 is not None # Todo: Make not nullable diff --git a/phasm/type5/solver.py b/phasm/type5/solver.py index 291cc8e..48bcc9b 100644 --- a/phasm/type5/solver.py +++ b/phasm/type5/solver.py @@ -3,7 +3,7 @@ from typing import Any from ..ourlang import Module from .constraints import ConstraintBase, Context from .fromast import phasm_type5_generate_constraints -from .typeexpr import TypeExpr, TypeVariable +from .typeexpr import TypeExpr, TypeVariable, replace_variable from .unify import ReplaceVariable MAX_RESTACK_COUNT = 10 # 100 @@ -47,10 +47,12 @@ def phasm_type5(inp: Module[Any], verbose: bool = False) -> None: if isinstance(action, ReplaceVariable): action_var: TypeExpr = action.var while isinstance(action_var, TypeVariable) and action_var in placeholder_types: + # TODO: Does this still happen? action_var = placeholder_types[action_var] action_typ: TypeExpr = action.typ while isinstance(action_typ, TypeVariable) and action_typ in placeholder_types: + # TODO: Does this still happen? action_typ = placeholder_types[action_typ] # print(inp.build.type5_name(action_var), ':=', inp.build.type5_name(action_typ)) @@ -62,6 +64,11 @@ def phasm_type5(inp: Module[Any], verbose: bool = False) -> None: action_typ, action_var = action_var, action_typ if isinstance(action_var, TypeVariable): + # Ensure all existing found types are updated + placeholder_types = { + k: replace_variable(v, action_var, action_typ) + for k, v in placeholder_types.items() + } placeholder_types[action_var] = action_typ for oth_const in new_constraint_list + constraint_list: diff --git a/phasm/type5/typeexpr.py b/phasm/type5/typeexpr.py index 3ae8e84..33b24ae 100644 --- a/phasm/type5/typeexpr.py +++ b/phasm/type5/typeexpr.py @@ -19,6 +19,9 @@ class AtomicType(TypeExpr): def __init__(self, name: str) -> None: super().__init__(Star(), name) + def __hash__(self) -> int: + return hash((self.kind, self.name)) + @dataclass class TypeLevelNat(TypeExpr): value: int @@ -42,8 +45,6 @@ class TypeVariable(TypeExpr): @dataclass class TypeConstructor(TypeExpr): - name: str - def __init__(self, kind: Arrow, name: str) -> None: super().__init__(kind, name) @@ -145,3 +146,33 @@ def replace_variable(expr: TypeExpr, var: TypeVariable, rep_expr: TypeExpr) -> T ) raise NotImplementedError + +def instantiate( + expr: TypeExpr, + known_map: dict[TypeVariable, TypeVariable], + make_variable: Callable[[KindExpr, str], TypeVariable], + ) -> TypeExpr: + if isinstance(expr, AtomicType): + return expr + + if isinstance(expr, TypeLevelNat): + return expr + + if isinstance(expr, TypeVariable): + known_map.setdefault(expr, make_variable(expr.kind, expr.name)) + return known_map[expr] + + if isinstance(expr, TypeConstructor): + return expr + + if isinstance(expr, TypeApplication): + new_constructor = instantiate(expr.constructor, known_map, make_variable) + + assert isinstance(new_constructor, TypeConstructor | TypeApplication | TypeVariable) # type hint + + return TypeApplication( + constructor=new_constructor, + argument=instantiate(expr.argument, known_map, make_variable), + ) + + raise NotImplementedError(expr) diff --git a/phasm/typeclass/__init__.py b/phasm/typeclass/__init__.py new file mode 100644 index 0000000..53dcae9 --- /dev/null +++ b/phasm/typeclass/__init__.py @@ -0,0 +1,8 @@ +from dataclasses import dataclass + +from ..type5.typeexpr import TypeVariable + +@dataclass +class TypeClass: + name: str + variables: list[TypeVariable] diff --git a/tests/integration/runners.py b/tests/integration/runners.py index 188627e..c58fdbf 100644 --- a/tests/integration/runners.py +++ b/tests/integration/runners.py @@ -41,7 +41,6 @@ class RunnerBase: """ self.phasm_ast = phasm_parse(self.phasm_code) phasm_type5(self.phasm_ast, verbose=verbose) - phasm_type3(self.phasm_ast, verbose=False) def compile_ast(self) -> None: """