From f6b4f7c20aadfe79add7e2996ddbc3291ea418dd Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Sat, 12 Jul 2025 17:28:52 +0200 Subject: [PATCH] First attempts --- phasm/build/base.py | 4 - phasm/codestyle.py | 11 +- phasm/compiler.py | 57 +++--- phasm/ourlang.py | 58 +++--- phasm/parser.py | 52 +++--- phasm/type3/constraintsgenerator.py | 136 +++++++------- phasm/type3/functions.py | 276 ++++++++++++++-------------- phasm/type3/typeclasses.py | 17 +- phasm/type3/types.py | 40 ++++ tests/integration/helpers.py | 10 +- 10 files changed, 356 insertions(+), 305 deletions(-) diff --git a/phasm/build/base.py b/phasm/build/base.py index 579c147..738b35c 100644 --- a/phasm/build/base.py +++ b/phasm/build/base.py @@ -6,10 +6,6 @@ Contains nothing but the explicit compiler builtins. from typing import Any, Callable, NamedTuple, Type from warnings import warn -from ..type3.functions import ( - TypeConstructorVariable, - TypeVariable, -) from ..type3.routers import ( NoRouteForTypeException, TypeApplicationRouter, diff --git a/phasm/codestyle.py b/phasm/codestyle.py index 1209c70..b97dfda 100644 --- a/phasm/codestyle.py +++ b/phasm/codestyle.py @@ -6,7 +6,7 @@ It's intented to be a "any color, as long as it's black" kind of renderer from typing import Any, Generator from . import ourlang -from .type3.types import Type3, TypeApplication_Struct +from .type3.types import Type3, TypeApplication_Struct, expect_function_type def phasm_render(inp: ourlang.Module[Any]) -> str: @@ -141,12 +141,15 @@ def function(inp: ourlang.Function) -> str: if inp.imported: result += '@imported\n' + params = [type3(x) for x in expect_function_type(inp.type3)] + return_type = params.pop() + args = ', '.join( - f'{p.name}: {type3(p.type3)}' - for p in inp.posonlyargs + f'{p_name}: {p_type3}' + for p_name, p_type3 in zip(inp.param_names, params, strict=True) ) - result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n' + result += f'def {inp.name}({args}) -> {return_type}:\n' if inp.imported: result += ' pass\n' diff --git a/phasm/compiler.py b/phasm/compiler.py index 439828a..35bd05a 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -21,6 +21,7 @@ from .type3.types import ( TypeConstructor_Function, TypeConstructor_StaticArray, TypeConstructor_Tuple, + expect_function_type, ) from .wasm import ( WasmTypeFloat32, @@ -197,9 +198,9 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl return if isinstance(inp, ourlang.VariableReference): - if isinstance(inp.variable, ourlang.FunctionParam): - wgn.add_statement('local.get', '${}'.format(inp.variable.name)) - return + # if isinstance(inp.variable, ourlang.FunctionParam): # TODO + # wgn.add_statement('local.get', '${}'.format(inp.variable.name)) + # return if isinstance(inp.variable, ourlang.ModuleConstantDef): assert inp.type3 is not None, TYPE3_ASSERTION_ERROR @@ -223,7 +224,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl type_var_map: dict[TypeVariable, Type3] = {} - for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True): + param_types = expect_function_type(inp.operator.type3) + for type_var, arg_expr in zip(param_types, [inp.left, inp.right, inp], strict=True): assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR if isinstance(type_var, Type3): @@ -252,7 +254,8 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl # FIXME: Duplicate code with BinaryOp type_var_map = {} - for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True): + param_types = expect_function_type(inp.function.type3) + for type_var, arg_expr in zip(param_types, inp.arguments + [inp], strict=True): assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR if isinstance(type_var, Type3): @@ -277,13 +280,7 @@ def expression(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], inp: ourl return if isinstance(inp.function, ourlang.FunctionParam): - assert isinstance(inp.function.type3.application.constructor, TypeConstructor_Function) - - params = [ - type3(mod, x) - for x in inp.function.type3.application.arguments - ] - + params = [type3(mod, x) for x in expect_function_type(inp.function.type3)] result = params.pop() wgn.add_statement('local.get', '${}'.format(inp.function.name)) @@ -388,27 +385,26 @@ def statement(wgn: WasmGenerator, mod: ourlang.Module[WasmGenerator], fun: ourla raise NotImplementedError(statement, inp) -def function_argument(mod: ourlang.Module[WasmGenerator], inp: ourlang.FunctionParam) -> wasm.Param: - """ - Compile: function argument - """ - return (inp.name, type3(mod, inp.type3), ) - def import_(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Import: """ Compile: imported function """ assert inp.imported + param_types = [type3(mod, x) for x in expect_function_type(inp.type3)] + return_type = param_types.pop() + + params = [ + (p_name, p_type) + for p_name, p_type in zip(inp.param_names, param_types, strict=True) + ] + return wasm.Import( inp.imported, inp.name, inp.name, - [ - function_argument(mod, x) - for x in inp.posonlyargs - ], - type3(mod, inp.returns_type3) + params, + return_type, ) def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm.Function: @@ -425,18 +421,23 @@ def function(mod: ourlang.Module[WasmGenerator], inp: ourlang.Function) -> wasm. for stat in inp.statements: statement(wgn, mod, inp, stat) + param_types = [type3(mod, x) for x in expect_function_type(inp.type3)] + return_type = param_types.pop() + + params = [ + (p_name, p_type) + for p_name, p_type in zip(inp.param_names, param_types, strict=True) + ] + return wasm.Function( inp.name, inp.name if inp.exported else None, - [ - function_argument(mod, x) - for x in inp.posonlyargs - ], + params, [ (k, v.wasm_type(), ) for k, v in wgn.locals.items() ], - type3(mod, inp.returns_type3), + return_type, wgn.statements ) diff --git a/phasm/ourlang.py b/phasm/ourlang.py index 5bd5597..d17b5da 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -4,9 +4,14 @@ Contains the syntax tree for ourlang from typing import Dict, Iterable, List, Optional, Union from .build.base import BuildBase -from .type3.functions import FunctionSignature, TypeVariableContext +from .type3.functions import TypeVariableContext from .type3.typeclasses import Type3ClassMethod -from .type3.types import Type3, TypeApplication_Struct +from .type3.types import ( + Type3, + TypeApplication_Struct, + expect_function_type, + expect_struct_type, +) class Expression: @@ -256,7 +261,7 @@ class StatementIf(Statement): self.test = test self.statements = [] self.else_statements = [] - + class FunctionParam: """ A parameter for a Function @@ -277,38 +282,50 @@ class Function: """ A function processes input and produces output """ - __slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'signature', 'returns_type3', 'posonlyargs', ) + __slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'signature', 'type3', 'param_names', ) name: str lineno: int exported: bool imported: Optional[str] statements: List[Statement] - signature: FunctionSignature - returns_type3: Type3 - posonlyargs: List[FunctionParam] + type3: Type3 + param_names: list[str] - def __init__(self, name: str, lineno: int, returns_type3: Type3) -> None: + def __init__(self, name: str, lineno: int, type3: Type3, param_names: list[str]) -> None: self.name = name self.lineno = lineno self.exported = False self.imported = None self.statements = [] - self.signature = FunctionSignature(TypeVariableContext(), []) - self.returns_type3 = returns_type3 - self.posonlyargs = [] + self.type3 = type3 + self.param_names = param_names + + def get_params(self) -> list[FunctionParam]: + param_types = list(expect_function_type(self.type3)) + param_types.pop() + + return [ + FunctionParam(p_name, p_type) + for p_name, p_type in zip(self.param_names, param_types) + ] class StructDefinition: """ The definition for a struct """ - __slots__ = ('struct_type3', 'lineno', ) + __slots__ = ('struct_type3', 'const_type3', 'lineno', ) struct_type3: Type3 + const_type3: Type3 lineno: int - def __init__(self, struct_type3: Type3, lineno: int) -> None: + def __init__(self, struct_type3: Type3, const_type3: Type3, lineno: int) -> None: + expect_struct_type(struct_type3) + expect_function_type(const_type3) + self.struct_type3 = struct_type3 + self.const_type3 = const_type3 self.lineno = lineno class StructConstructor(Function): @@ -322,18 +339,13 @@ class StructConstructor(Function): struct_type3: Type3 - def __init__(self, struct_type3: Type3) -> None: - super().__init__(f'@{struct_type3.name}@__init___@', -1, struct_type3) + def __init__(self, struct_type3: Type3, const_type3: Type3) -> None: + struct_params = expect_struct_type(struct_type3) + expect_function_type(const_type3) - assert isinstance(struct_type3.application, TypeApplication_Struct) + param_names = [x[0] for x in struct_params] - for mem, typ in struct_type3.application.arguments: - self.posonlyargs.append(FunctionParam(mem, typ, )) - self.signature.args.append(typ) - - self.signature.args.append(struct_type3) - - self.struct_type3 = struct_type3 + super().__init__(f'@{struct_type3.name}@__init___@', -1, const_type3, param_names) class ModuleConstantDef: """ diff --git a/phasm/parser.py b/phasm/parser.py index a182822..cd87c7e 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -49,7 +49,7 @@ def phasm_parse(source: str) -> Module[Generator]: our_visitor = OurVisitor(build) return our_visitor.visit_Module(res) -OurLocals = Dict[str, Union[FunctionParam]] # FIXME: Does it become easier if we add ModuleConstantDef to this dict? +OurLocals = Dict[str, FunctionParam] class OptimizerTransformer(ast.NodeTransformer): """ @@ -124,7 +124,7 @@ class OurVisitor[G]: ) module.types[res.struct_type3.name] = res.struct_type3 - module.functions[res.struct_type3.name] = StructConstructor(res.struct_type3) + module.functions[res.struct_type3.name] = StructConstructor(res.struct_type3, res.const_type3) # Store that the definition was done in this module for the formatter module.struct_definitions[res.struct_type3.name] = res @@ -156,24 +156,27 @@ class OurVisitor[G]: raise NotImplementedError(f'{node} on Module') def pre_visit_Module_FunctionDef(self, module: Module[G], node: ast.FunctionDef) -> Function: - function = Function(node.name, node.lineno, self.build.none_) - _not_implemented(not node.args.posonlyargs, 'FunctionDef.args.posonlyargs') + arg_names: list[str] + arg_types: list[Type3] for arg in node.args.args: if arg.annotation is None: _raise_static_error(node, 'Must give a argument type') - arg_type = self.visit_type(module, arg.annotation) + arg_names.append(arg.arg) + arg_types.append(self.visit_type(module, arg.annotation)) - # FIXME: Allow TypeVariable in the function signature - # This would also require FunctionParam to accept a placeholder + if node.returns is None: # Note: `-> None` would be a ast.Constant + _raise_static_error(node, 'Must give a return type') + arg_types.append(self.visit_type(module, node.returns)) - function.signature.args.append(arg_type) - function.posonlyargs.append(FunctionParam( - arg.arg, - arg_type, - )) + function = Function( + node.name, + node.lineno, + self.build.function(*arg_types), + arg_names, + ) _not_implemented(not node.args.vararg, 'FunctionDef.args.vararg') _not_implemented(not node.args.kwonlyargs, 'FunctionDef.args.kwonlyargs') @@ -181,8 +184,6 @@ class OurVisitor[G]: _not_implemented(not node.args.kwarg, 'FunctionDef.args.kwarg') _not_implemented(not node.args.defaults, 'FunctionDef.args.defaults') - # Do stmts at the end so we have the return value - for decorator in node.decorator_list: if isinstance(decorator, ast.Call): if not isinstance(decorator.func, ast.Name): @@ -213,12 +214,6 @@ class OurVisitor[G]: else: function.imported = 'imports' - if node.returns is None: # Note: `-> None` would be a ast.Constant - _raise_static_error(node, 'Must give a return type') - return_type = self.visit_type(module, node.returns) - function.signature.args.append(return_type) - function.returns_type3 = return_type - _not_implemented(not node.type_comment, 'FunctionDef.type_comment') return function @@ -249,7 +244,14 @@ class OurVisitor[G]: members[stmt.target.id] = self.visit_type(module, stmt.annotation) - return StructDefinition(module.build.struct(node.name, tuple(members.items())), node.lineno) + struct_type3 = module.build.struct(node.name, tuple(members.items())) + const_type3 = module.build.function(*members.values(), struct_type3) + + return StructDefinition( + const_type3, + struct_type3, + node.lineno, + ) def pre_visit_Module_AnnAssign(self, module: Module[G], node: ast.AnnAssign) -> ModuleConstantDef: if not isinstance(node.target, ast.Name): @@ -313,7 +315,7 @@ class OurVisitor[G]: our_locals: OurLocals = { x.name: x - for x in function.posonlyargs + for x in function.get_params() } for stmt in node.body: @@ -478,12 +480,12 @@ class OurVisitor[G]: if not isinstance(node.func.ctx, ast.Load): _raise_static_error(node, 'Must be load context') - func: Union[Function, FunctionParam, Type3ClassMethod] + func: Union[Function, Type3ClassMethod] if node.func.id in module.methods: func = module.methods[node.func.id] - elif node.func.id in our_locals: - func = our_locals[node.func.id] + # elif node.func.id in our_locals: # TODO + # func = our_locals[node.func.id] else: if node.func.id not in module.functions: _raise_static_error(node, 'Call to undefined function') diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index 7270973..262d3a0 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -20,13 +20,12 @@ from .constraints import ( from .functions import ( Constraint_TypeClassInstanceExists, FunctionArgument, - FunctionSignature, TypeVariable, TypeVariableApplication_Unary, TypeVariableContext, ) from .placeholders import PlaceholderForType -from .types import Type3, TypeApplication_Struct, TypeConstructor_Function +from .types import Type3, TypeApplication_Struct, TypeConstructor_Function, expect_function_type ConstraintGenerator = Generator[ConstraintBase, None, None] @@ -49,26 +48,17 @@ def expression_binary_op(ctx: Context, inp: ourlang.BinaryOp, phft: PlaceholderF return _expression_function_call( ctx, f'({inp.operator.name})', - inp.operator.signature, + inp.operator.type3, [inp.left, inp.right], inp, phft, ) def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: PlaceholderForType) -> ConstraintGenerator: - if isinstance(inp.function, ourlang.FunctionParam): - assert isinstance(inp.function.type3.application.constructor, TypeConstructor_Function) - signature = FunctionSignature( - TypeVariableContext(), - inp.function.type3.application.arguments, - ) - else: - signature = inp.function.signature - return _expression_function_call( ctx, inp.function.name, - signature, + inp.function.type3, inp.arguments, inp, phft, @@ -77,7 +67,7 @@ def expression_function_call(ctx: Context, inp: ourlang.FunctionCall, phft: Plac def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, phft: PlaceholderForType) -> ConstraintGenerator: yield SameTypeConstraint( ctx, - ctx.build.function(*(x.type3 for x in inp.function.posonlyargs), inp.function.returns_type3), + inp.function.type3, phft, comment=f'typeOf("{inp.function.name}") == typeOf({inp.function.name})', ) @@ -85,7 +75,7 @@ def expression_function_reference(ctx: Context, inp: ourlang.FunctionReference, def _expression_function_call( ctx: Context, func_name: str, - signature: FunctionSignature, + type3: Type3, arguments: list[ourlang.Expression], return_expr: ourlang.Expression, return_phft: PlaceholderForType, @@ -96,6 +86,8 @@ def _expression_function_call( A Binary operator functions pretty much the same as a function call with two arguments - it's only a syntactic difference. """ + param_types = expect_function_type(type3) + # First create placeholders for all arguments, and generate their constraints arg_placeholders = { arg_expr: PlaceholderForType([arg_expr]) @@ -115,82 +107,84 @@ def _expression_function_call( # subsituted - that's done by arg_placeholders. type_var_map = { x: PlaceholderForType([]) - for x in signature.args + for x in param_types if isinstance(x, TypeVariable) } - for constraint in signature.context.constraints: - if isinstance(constraint, Constraint_TypeClassInstanceExists): - yield MustImplementTypeClassConstraint( - ctx, - constraint.type_class3, - [type_var_map[x] for x in constraint.types], - ) - continue + # for constraint in signature.context.constraints: # TODO + # if isinstance(constraint, Constraint_TypeClassInstanceExists): + # yield MustImplementTypeClassConstraint( + # ctx, + # constraint.type_class3, + # [type_var_map[x] for x in constraint.types], + # ) + # continue - raise NotImplementedError(constraint) + # raise NotImplementedError(constraint) func_var_map = { x: PlaceholderForType([]) - for x in signature.args + for x in param_types if isinstance(x, FunctionArgument) } - # If some of the function arguments are functions, - # we need to deal with those separately. - for sig_arg in signature.args: - if not isinstance(sig_arg, FunctionArgument): - continue + # TODO + # # If some of the function arguments are functions, + # # we need to deal with those separately. + # for sig_arg in param_types: + # if not isinstance(sig_arg, FunctionArgument): + # continue - # Ensure that for all type variables in the function - # there are also type variables available - for func_arg in sig_arg.args: - if isinstance(func_arg, Type3): - continue + # # Ensure that for all type variables in the function + # # there are also type variables available + # for func_arg in sig_arg.args: + # if isinstance(func_arg, Type3): + # continue - type_var_map.setdefault(func_arg, PlaceholderForType([])) + # type_var_map.setdefault(func_arg, PlaceholderForType([])) - yield SameFunctionArgumentConstraint( - ctx, - func_var_map[sig_arg], - sig_arg, - type_var_map, - comment=f'Ensure `{sig_arg.name}` matches in {signature}', - ) + # yield SameFunctionArgumentConstraint( + # ctx, + # func_var_map[sig_arg], + # sig_arg, + # type_var_map, + # comment=f'Ensure `{sig_arg.name}` matches in {type}', + # ) - # If some of the function arguments are type constructors, - # we need to deal with those separately. - # That is, given `foo :: t a -> a` we need to ensure - # that both a's are the same. - for sig_arg in signature.args: - if isinstance(sig_arg, Type3): - # Not a type variable at all - continue + # TODO + # # If some of the function arguments are type constructors, + # # we need to deal with those separately. + # # That is, given `foo :: t a -> a` we need to ensure + # # that both a's are the same. + # for sig_arg in param_types: + # if isinstance(sig_arg, Type3): + # # Not a type variable at all + # continue - if isinstance(sig_arg, FunctionArgument): - continue + # if isinstance(sig_arg, FunctionArgument): + # continue - if sig_arg.application.constructor is None: - # Not a type variable for a type constructor - continue + # if sig_arg.application.constructor is None: + # # Not a type variable for a type constructor + # continue - if not isinstance(sig_arg.application, TypeVariableApplication_Unary): - raise NotImplementedError(sig_arg.application) + # if not isinstance(sig_arg.application, TypeVariableApplication_Unary): + # raise NotImplementedError(sig_arg.application) - if sig_arg.application.arguments not in type_var_map: - # e.g., len :: t a -> u32 - # i.e. "a" does not matter at all - continue + # if sig_arg.application.arguments not in type_var_map: + # # e.g., len :: t a -> u32 + # # i.e. "a" does not matter at all + # continue - yield SameTypeArgumentConstraint( - ctx, - type_var_map[sig_arg], - type_var_map[sig_arg.application.arguments], - comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {signature}', - ) + # yield SameTypeArgumentConstraint( + # ctx, + # type_var_map[sig_arg], + # type_var_map[sig_arg.application.arguments], + # comment=f'Ensure `{sig_arg.application.arguments.name}` matches in {type3}', + # ) # Lastly, tie the signature and expression together - for arg_no, (sig_part, arg_expr) in enumerate(zip(signature.args, arguments + [return_expr], strict=True)): + for arg_no, (sig_part, arg_expr) in enumerate(zip(param_types, arguments + [return_expr], strict=True)): if arg_no == len(arguments): comment = f'The type of a function call to {func_name} is the same as the type that the function returns' else: @@ -320,7 +314,7 @@ def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.Statement # a ~ c => c := a # a ~ a => ignore - yield SameTypeConstraint(ctx, fun.returns_type3, phft, + yield SameTypeConstraint(ctx, fun.type3.application.arguments[-1], phft, comment=f'The type of the value returned from function {fun.name} should match its return type') def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator: diff --git a/phasm/type3/functions.py b/phasm/type3/functions.py index d10e2b1..2a882f1 100644 --- a/phasm/type3/functions.py +++ b/phasm/type3/functions.py @@ -1,194 +1,194 @@ -from __future__ import annotations +# from __future__ import annotations -from typing import TYPE_CHECKING, Any, Hashable, Iterable, List +# from typing import TYPE_CHECKING, Any, Hashable, Iterable, List -if TYPE_CHECKING: - from .typeclasses import Type3Class - from .types import Type3 +# if TYPE_CHECKING: +# from .typeclasses import Type3Class +# from .types import Type3 -class TypeVariableApplication_Base[T: Hashable, S: Hashable]: - """ - Records the constructor and arguments used to create this type. +# class TypeVariableApplication_Base[T: Hashable, S: Hashable]: +# """ +# Records the constructor and arguments used to create this type. - Nullary types, or types of kind *, have both arguments set to None. - """ - constructor: T - arguments: S +# Nullary types, or types of kind *, have both arguments set to None. +# """ +# constructor: T +# arguments: S - def __init__(self, constructor: T, arguments: S) -> None: - self.constructor = constructor - self.arguments = arguments +# def __init__(self, constructor: T, arguments: S) -> None: +# self.constructor = constructor +# self.arguments = arguments - def __hash__(self) -> int: - return hash((self.constructor, self.arguments, )) +# def __hash__(self) -> int: +# return hash((self.constructor, self.arguments, )) - def __eq__(self, other: Any) -> bool: - if not isinstance(other, TypeVariableApplication_Base): - raise NotImplementedError +# def __eq__(self, other: Any) -> bool: +# if not isinstance(other, TypeVariableApplication_Base): +# raise NotImplementedError - return (self.constructor == other.constructor # type: ignore[no-any-return] - and self.arguments == other.arguments) +# return (self.constructor == other.constructor # type: ignore[no-any-return] +# and self.arguments == other.arguments) - def __repr__(self) -> str: - return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})' +# def __repr__(self) -> str: +# return f'{self.__class__.__name__}({self.constructor!r}, {self.arguments!r})' -class TypeVariable: - """ - Types variable are used in function definition. +# class TypeVariable2: +# """ +# Types variable are used in function definition. - They are used in places where you don't know the exact type. - They are different from PlaceholderForType, as those are instanced - during type checking. These type variables are used solely in the - function's definition - """ - __slots__ = ('name', 'application', ) +# They are used in places where you don't know the exact type. +# They are different from PlaceholderForType, as those are instanced +# during type checking. These type variables are used solely in the +# function's definition +# """ +# __slots__ = ('name', 'application', ) - name: str - application: TypeVariableApplication_Base[Any, Any] +# name: str +# application: TypeVariableApplication_Base[Any, Any] - def __init__(self, name: str, application: TypeVariableApplication_Base[Any, Any]) -> None: - self.name = name - self.application = application +# def __init__(self, name: str, application: TypeVariableApplication_Base[Any, Any]) -> None: +# self.name = name +# self.application = application - def __hash__(self) -> int: - return hash((self.name, self.application, )) +# def __hash__(self) -> int: +# return hash((self.name, self.application, )) - def __eq__(self, other: Any) -> bool: - if not isinstance(other, TypeVariable): - raise NotImplementedError +# def __eq__(self, other: Any) -> bool: +# if not isinstance(other, TypeVariable): +# raise NotImplementedError - return (self.name == other.name - and self.application == other.application) +# return (self.name == other.name +# and self.application == other.application) - def __repr__(self) -> str: - return f'TypeVariable({repr(self.name)})' +# def __repr__(self) -> str: +# return f'TypeVariable({repr(self.name)})' -class TypeVariableApplication_Nullary(TypeVariableApplication_Base[None, None]): - """ - For the type for this function argument it's not relevant if it was constructed. - """ +# class TypeVariableApplication_Nullary(TypeVariableApplication_Base[None, None]): +# """ +# For the type for this function argument it's not relevant if it was constructed. +# """ -def make_typevar(name: str) -> TypeVariable: - """ - Helper function to make a type variable for a non-constructed type. - """ - return TypeVariable(name, TypeVariableApplication_Nullary(None, None)) +# def make_typevar(name: str) -> TypeVariable: +# """ +# Helper function to make a type variable for a non-constructed type. +# """ +# return TypeVariable(name, TypeVariableApplication_Nullary(None, None)) -class TypeConstructorVariable: - """ - Types constructor variable are used in function definition. +# class TypeConstructorVariable: +# """ +# Types constructor variable are used in function definition. - They are a lot like TypeVariable, except that they represent a - type constructor rather than a type directly. +# They are a lot like TypeVariable, except that they represent a +# type constructor rather than a type directly. - For now, we only have type constructor variables for kind - * -> *. - """ - __slots__ = ('name', ) +# For now, we only have type constructor variables for kind +# * -> *. +# """ +# __slots__ = ('name', ) - name: str +# name: str - def __init__(self, name: str) -> None: - self.name = name +# def __init__(self, name: str) -> None: +# self.name = name - def __hash__(self) -> int: - return hash((self.name, )) +# def __hash__(self) -> int: +# return hash((self.name, )) - def __eq__(self, other: Any) -> bool: - if other is None: - return False +# def __eq__(self, other: Any) -> bool: +# if other is None: +# return False - if not isinstance(other, TypeConstructorVariable): - raise NotImplementedError(other) +# if not isinstance(other, TypeConstructorVariable): +# raise NotImplementedError(other) - return (self.name == other.name) +# return (self.name == other.name) - def __call__(self, tvar: TypeVariable) -> 'TypeVariable': - return TypeVariable( - self.name + ' ' + tvar.name, - TypeVariableApplication_Unary(self, tvar) - ) +# def __call__(self, tvar: TypeVariable) -> 'TypeVariable': +# return TypeVariable( +# self.name + ' ' + tvar.name, +# TypeVariableApplication_Unary(self, tvar) +# ) - def __repr__(self) -> str: - return f'TypeConstructorVariable({self.name!r})' +# def __repr__(self) -> str: +# return f'TypeConstructorVariable({self.name!r})' -class TypeVariableApplication_Unary(TypeVariableApplication_Base[TypeConstructorVariable, TypeVariable]): - """ - The type for this function argument should be constructed from a type constructor. +# class TypeVariableApplication_Unary(TypeVariableApplication_Base[TypeConstructorVariable, TypeVariable]): +# """ +# The type for this function argument should be constructed from a type constructor. - And we need to know what construtor that was, since that's the one we support. - """ +# And we need to know what construtor that was, since that's the one we support. +# """ -class ConstraintBase: - __slots__ = () +# class ConstraintBase: +# __slots__ = () -class Constraint_TypeClassInstanceExists(ConstraintBase): - __slots__ = ('type_class3', 'types', ) +# class Constraint_TypeClassInstanceExists(ConstraintBase): +# __slots__ = ('type_class3', 'types', ) - type_class3: 'Type3Class' - types: list[TypeVariable] +# type_class3: 'Type3Class' +# types: list[TypeVariable] - def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None: - self.type_class3 = type_class3 - self.types = list(types) +# def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None: +# self.type_class3 = type_class3 +# self.types = list(types) - # Sanity check. AFAIK, if you have a multi-parameter type class, - # you can only add a constraint by supplying types for all variables - assert len(self.type_class3.args) == len(self.types) +# # Sanity check. AFAIK, if you have a multi-parameter type class, +# # you can only add a constraint by supplying types for all variables +# assert len(self.type_class3.args) == len(self.types) - def __str__(self) -> str: - return self.type_class3.name + ' ' + ' '.join(x.name for x in self.types) +# def __str__(self) -> str: +# return self.type_class3.name + ' ' + ' '.join(x.name for x in self.types) - def __repr__(self) -> str: - return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})' +# def __repr__(self) -> str: +# return f'Constraint_TypeClassInstanceExists({self.type_class3.name}, {self.types!r})' -class TypeVariableContext: - __slots__ = ('constraints', ) +# class TypeVariableContext: +# __slots__ = ('constraints', ) - constraints: list[ConstraintBase] +# constraints: list[ConstraintBase] - def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None: - self.constraints = list(constraints) +# def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None: +# self.constraints = list(constraints) - def __copy__(self) -> 'TypeVariableContext': - return TypeVariableContext(self.constraints) +# def __copy__(self) -> 'TypeVariableContext': +# return TypeVariableContext(self.constraints) - def __str__(self) -> str: - if not self.constraints: - return '' +# def __str__(self) -> str: +# if not self.constraints: +# return '' - return '(' + ', '.join(str(x) for x in self.constraints) + ') => ' +# return '(' + ', '.join(str(x) for x in self.constraints) + ') => ' - def __repr__(self) -> str: - return f'TypeVariableContext({self.constraints!r})' +# def __repr__(self) -> str: +# return f'TypeVariableContext({self.constraints!r})' -class FunctionArgument: - __slots__ = ('args', 'name', ) +# class FunctionArgument: +# __slots__ = ('args', 'name', ) - args: list[Type3 | TypeVariable] - name: str +# args: list[Type3 | TypeVariable] +# name: str - def __init__(self, args: list[Type3 | TypeVariable]) -> None: - self.args = args +# def __init__(self, args: list[Type3 | TypeVariable]) -> None: +# self.args = args - self.name = '(' + ' -> '.join(x.name for x in args) + ')' +# self.name = '(' + ' -> '.join(x.name for x in args) + ')' -class FunctionSignature: - __slots__ = ('context', 'args', ) +# class FunctionSignature2: +# __slots__ = ('context', 'args', ) - context: TypeVariableContext - args: List[Type3 | TypeVariable | FunctionArgument] +# context: TypeVariableContext +# args: List[Type3 | TypeVariable | FunctionArgument] - def __init__(self, context: TypeVariableContext, args: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]) -> None: - self.context = context.__copy__() - self.args = list( - FunctionArgument(x) if isinstance(x, list) else x - for x in args - ) +# def __init__(self, context: TypeVariableContext, args: Iterable[Type3 | TypeVariable | list[Type3 | TypeVariable]]) -> None: +# self.context = context.__copy__() +# self.args = list( +# FunctionArgument(x) if isinstance(x, list) else x +# for x in args +# ) - def __str__(self) -> str: - return str(self.context) + ' -> '.join(x.name for x in self.args) +# def __str__(self) -> str: +# return str(self.context) + ' -> '.join(x.name for x in self.args) - def __repr__(self) -> str: - return f'FunctionSignature({self.context!r}, {self.args!r})' +# def __repr__(self) -> str: +# return f'FunctionSignature({self.context!r}, {self.args!r})' diff --git a/phasm/type3/typeclasses.py b/phasm/type3/typeclasses.py index 02c6ad0..3fd1893 100644 --- a/phasm/type3/typeclasses.py +++ b/phasm/type3/typeclasses.py @@ -3,29 +3,30 @@ from typing import Dict, Iterable, List, Mapping, Optional from .functions import ( Constraint_TypeClassInstanceExists, ConstraintBase, - FunctionSignature, TypeConstructorVariable, TypeVariable, TypeVariableContext, ) -from .types import Type3 +from .types import Type3, expect_function_type class Type3ClassMethod: - __slots__ = ('name', 'signature', ) + __slots__ = ('name', 'type3', ) name: str - signature: FunctionSignature + type3: Type3 + + def __init__(self, name: str, type3: Type3) -> None: + expect_function_type(type3) - def __init__(self, name: str, signature: FunctionSignature) -> None: self.name = name - self.signature = signature + self.type3 = type3 def __str__(self) -> str: - return f'{self.name} :: {self.signature}' + return f'{self.name} :: {self.type3}' def __repr__(self) -> str: - return f'Type3ClassMethod({repr(self.name)}, {repr(self.signature)})' + return f'Type3ClassMethod({repr(self.name)}, {repr(self.type3)})' Type3ClassArgs = tuple[TypeVariable] | tuple[TypeVariable, TypeVariable] | tuple[TypeConstructorVariable] diff --git a/phasm/type3/types.py b/phasm/type3/types.py index 75fb068..7526de7 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -97,6 +97,26 @@ class TypeApplication_Nullary(TypeApplication_Base[None, None]): There was no constructor used to create this type - it's a 'simple' type like u32 """ +def make_type(name: str) -> Type3: + """ + Make a type of kind * + """ + return Type3(name, TypeApplication_Nullary(None, None)) + +class TypeApplication_Variable(TypeApplication_Base[None, None]): + """ + We don't know what the actual type is, it's to be determined. + """ + +def make_type_variable(name: str) -> Type3: + """ + Make a type variable + + Type variables always have kind *. But you can construct a type + from a type constructor using a type variable. + """ + return Type3(name, TypeApplication_Variable(None, None)) + class IntType3(KindArgument): """ Sometimes you can have an int on the type level, e.g. when using static arrays @@ -282,6 +302,16 @@ class TypeConstructor_Function(TypeConstructor_TypeStar): def make_name(self, key: Tuple[Type3, ...]) -> str: return 'Callable[' + ', '.join(x.name for x in key) + ']' +def expect_function_type(typ: Type3) -> list[Type3]: + """ + Checks for a function type and returns its argument types. + + Raises an AssertionError if typ is not a function type. + """ + assert isinstance(typ.application, TypeApplication_TypeStar) + assert isinstance(typ.application.constructor, TypeConstructor_Function) + return list(typ.application.arguments) + class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]]): """ Constructs struct types @@ -308,3 +338,13 @@ class TypeConstructor_Struct(TypeConstructor_Base[tuple[tuple[str, Type3], ...]] class TypeApplication_Struct(TypeApplication_Base[TypeConstructor_Struct, tuple[tuple[str, Type3], ...]]): pass + +def expect_struct_type(typ: Type3) -> list[tuple[str, Type3]]: + """ + Checks for a struct type and returns its argument types. + + Raises an AssertionError if typ is not a struct type. + """ + assert isinstance(typ.application, TypeApplication_Struct) + assert isinstance(typ.application.constructor, TypeConstructor_Struct) + return list(typ.application.arguments) diff --git a/tests/integration/helpers.py b/tests/integration/helpers.py index c6d6438..0a89ff6 100644 --- a/tests/integration/helpers.py +++ b/tests/integration/helpers.py @@ -115,8 +115,9 @@ class Suite: if do_format_check: assert self.code_py == '\n' + phasm_render(runner.phasm_ast) # \n for formatting in tests - func_args = [x.type3 for x in runner.phasm_ast.functions[func_name].posonlyargs] - if len(func_args) != len(args): + param_types = type3types.expect_function_type(runner.phasm_ast.functions[func_name].type3) + param_types.pop() + if len(param_types) != len(args): raise RuntimeError(f'Invalid number of args for {func_name}') wasm_args: List[Union[float, int]] = [] @@ -125,7 +126,7 @@ class Suite: write_header(sys.stderr, 'Memory (pre alloc)') runner.interpreter_dump_memory(sys.stderr) - for arg, arg_typ in zip(args, func_args, strict=True): + for arg, arg_typ in zip(args, param_types, strict=True): arg_typ_info = runner.phasm_ast.build.type_info_map.get(arg_typ.name) if arg_typ_info and (arg_typ_info.wasm_type is WasmTypeInt32 or arg_typ_info.wasm_type is WasmTypeInt64): @@ -287,7 +288,8 @@ def _load_memory_stored_returned_value( func_name: str, wasm_value: Any, ) -> Any: - ret_type3 = runner.phasm_ast.functions[func_name].returns_type3 + param_types = type3types.expect_function_type(runner.phasm_ast.functions[func_name].type3) + ret_type3 = param_types[-1] if ret_type3.name in runner.phasm_ast.build.type_info_map: type_info = runner.phasm_ast.build.type_info_map[ret_type3.name]