From b5a28daebf00b2524185de4e145a2c66f0020c0f Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Thu, 24 Nov 2022 14:49:17 +0100 Subject: [PATCH] Ripping out Type2 (type_var) system --- phasm/codestyle.py | 60 +- phasm/compiler.py | 117 ++-- phasm/ourlang.py | 27 +- phasm/parser.py | 72 +- phasm/type3/types.py | 2 + phasm/typer.py | 193 ------ phasm/typing.py | 653 ------------------ tests/integration/runners.py | 2 - tests/integration/test_code/__init__.py | 0 tests/integration/test_code/test_typing.py | 20 - .../integration/test_lang/test_primitives.py | 29 +- .../test_lang/test_static_array.py | 2 +- 12 files changed, 141 insertions(+), 1036 deletions(-) delete mode 100644 phasm/typer.py delete mode 100644 phasm/typing.py delete mode 100644 tests/integration/test_code/__init__.py delete mode 100644 tests/integration/test_code/test_typing.py diff --git a/phasm/codestyle.py b/phasm/codestyle.py index 1af79ca..bf58a00 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 Generator, Optional from . import ourlang -from . import typing +from .type3.types import TYPE3_ASSERTION_ERROR, Type3, Type3OrPlaceholder def phasm_render(inp: ourlang.Module) -> str: """ @@ -15,35 +15,43 @@ def phasm_render(inp: ourlang.Module) -> str: return module(inp) Statements = Generator[str, None, None] +# +# def type_var(inp: Optional[typing.TypeVar]) -> str: +# """ +# Render: type's name +# """ +# assert inp is not None, typing.ASSERTION_ERROR +# +# mtyp = typing.simplify(inp) +# if mtyp is None: +# raise NotImplementedError(f'Rendering type {inp}') +# +# return mtyp -def type_var(inp: Optional[typing.TypeVar]) -> str: +def type3(inp: Type3OrPlaceholder) -> str: """ Render: type's name """ - assert inp is not None, typing.ASSERTION_ERROR + assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR - mtyp = typing.simplify(inp) - if mtyp is None: - raise NotImplementedError(f'Rendering type {inp}') + return inp.name - return mtyp - -def struct_definition(inp: typing.TypeStruct) -> str: - """ - Render: TypeStruct's definition - """ - result = f'class {inp.name}:\n' - for mem in inp.members: # TODO: Broken after new type system - raise NotImplementedError('Structs broken after new type system') - # result += f' {mem.name}: {type_(mem.type)}\n' - - return result +# def struct_definition(inp: typing.TypeStruct) -> str: +# """ +# Render: TypeStruct's definition +# """ +# result = f'class {inp.name}:\n' +# for mem in inp.members: # TODO: Broken after new type system +# raise NotImplementedError('Structs broken after new type system') +# # result += f' {mem.name}: {type_(mem.type)}\n' +# +# return result def constant_definition(inp: ourlang.ModuleConstantDef) -> str: """ Render: Module Constant's definition """ - return f'{inp.name}: {type_var(inp.type_var)} = {expression(inp.constant)}\n' + return f'{inp.name}: {type3(inp.type3)} = {expression(inp.constant)}\n' def expression(inp: ourlang.Expression) -> str: """ @@ -70,7 +78,7 @@ def expression(inp: ourlang.Expression) -> str: return f'{inp.operator}({expression(inp.right)})' if inp.operator == 'cast': - mtyp = type_var(inp.type_var) + mtyp = type3(inp.type3) if mtyp is None: raise NotImplementedError(f'Casting to type {inp.type_var}') @@ -150,11 +158,11 @@ def function(inp: ourlang.Function) -> str: result += '@imported\n' args = ', '.join( - f'{p.name}: {type_var(p.type_var)}' + f'{p.name}: {type3(p.type3)}' for p in inp.posonlyargs ) - result += f'def {inp.name}({args}) -> {type_var(inp.returns_type_var)}:\n' + result += f'def {inp.name}({args}) -> {type3(inp.returns_type3)}:\n' if inp.imported: result += ' pass\n' @@ -172,10 +180,10 @@ def module(inp: ourlang.Module) -> str: """ result = '' - for struct in inp.structs.values(): - if result: - result += '\n' - result += struct_definition(struct) + # for struct in inp.structs.values(): + # if result: + # result += '\n' + # result += struct_definition(struct) for cdef in inp.constant_defs.values(): if result: diff --git a/phasm/compiler.py b/phasm/compiler.py index 5d8e490..5a01c4c 100644 --- a/phasm/compiler.py +++ b/phasm/compiler.py @@ -7,7 +7,6 @@ import struct from . import codestyle from . import ourlang -from . import typing from .type3 import types as type3types from . import wasm @@ -22,45 +21,38 @@ def phasm_compile(inp: ourlang.Module) -> wasm.Module: """ return module(inp) -def type_var(inp: Optional[typing.TypeVar]) -> wasm.WasmType: +def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType: """ Compile: type Types are used for example in WebAssembly function parameters and return types. """ - assert inp is not None, typing.ASSERTION_ERROR + assert isinstance(inp, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR - mtyp = typing.simplify(inp) - - if mtyp == 'u8': + if inp is type3types.u8: # WebAssembly has only support for 32 and 64 bits # So we need to store more memory per byte return wasm.WasmTypeInt32() - if mtyp == 'u32': + if inp is type3types.u32: return wasm.WasmTypeInt32() - if mtyp == 'u64': + if inp is type3types.u64: return wasm.WasmTypeInt64() - if mtyp == 'i32': + if inp is type3types.i32: return wasm.WasmTypeInt32() - if mtyp == 'i64': + if inp is type3types.i64: return wasm.WasmTypeInt64() - if mtyp == 'f32': + if inp is type3types.f32: return wasm.WasmTypeFloat32() - if mtyp == 'f64': + if inp is type3types.f64: return wasm.WasmTypeFloat64() - assert inp is not None, typing.ASSERTION_ERROR - tc_type = inp.get_type() - if tc_type is None: - raise NotImplementedError(type_var, inp) - # if tc_prim.primitive is typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY: # # StaticArray, Tuples and Structs are passed as pointer # # And pointers are i32 @@ -72,7 +64,7 @@ def type_var(inp: Optional[typing.TypeVar]) -> wasm.WasmType: # # And pointers are i32 # return wasm.WasmTypeInt32() - raise NotImplementedError(inp, mtyp) + raise NotImplementedError(type3, inp) # Operators that work for i32, i64, f32, f64 OPERATOR_MAP = { @@ -147,42 +139,35 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: Compile: Any expression """ if isinstance(inp, ourlang.ConstantPrimitive): - assert inp.type3 is not None, typing.ASSERTION_ERROR + assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR - - assert inp.type_var is not None, typing.ASSERTION_ERROR - - stp = typing.simplify(inp.type_var) - if stp is None: - raise NotImplementedError(f'Constants with type {inp.type_var}') - - if stp == 'u8': + if inp.type3 is type3types.u8: # No native u8 type - treat as i32, with caution assert isinstance(inp.value, int) wgn.i32.const(inp.value) return - if stp in ('i32', 'u32'): + if inp.type3 is type3types.i32 or inp.type3 is type3types.u32: assert isinstance(inp.value, int) wgn.i32.const(inp.value) return - if stp in ('i64', 'u64'): + if inp.type3 is type3types.i64 or inp.type3 is type3types.u64: assert isinstance(inp.value, int) wgn.i64.const(inp.value) return - if stp == 'f32': + if inp.type3 is type3types.f32: assert isinstance(inp.value, float) wgn.f32.const(inp.value) return - if stp == 'f64': + if inp.type3 is type3types.f64: assert isinstance(inp.value, float) wgn.f64.const(inp.value) return - raise NotImplementedError(f'Constants with type {stp}') + raise NotImplementedError(f'Constants with type {inp.type3}') if isinstance(inp, ourlang.VariableReference): if isinstance(inp.variable, ourlang.FunctionParam): @@ -190,10 +175,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: return if isinstance(inp.variable, ourlang.ModuleConstantDef): - assert inp.variable.type_var is not None, typing.ASSERTION_ERROR - tc_type = inp.variable.type_var.get_type() - if tc_type is None: - raise NotImplementedError(expression, inp, inp.variable.type_var) + assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR # TODO: Broken after new type system # if isinstance(inp.type, typing.TypeTuple): @@ -212,13 +194,6 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: assert inp.variable.data_block is None, 'Primitives are not memory stored' - assert inp.variable.type_var is not None, typing.ASSERTION_ERROR - mtyp = typing.simplify(inp.variable.type_var) - if mtyp is None: - # In the future might extend this by having structs or tuples - # as members of struct or tuples - raise NotImplementedError(expression, inp, inp.type_var) - expression(wgn, inp.variable.constant) return @@ -228,49 +203,49 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: expression(wgn, inp.left) expression(wgn, inp.right) - assert inp.type_var is not None, typing.ASSERTION_ERROR - mtyp = typing.simplify(inp.type_var) + assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR + # FIXME: Re-implement build-in operators - if mtyp == 'u8': + if inp.type3 is type3types.u8: if operator := U8_OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'i32.{operator}') return - if mtyp == 'u32': + if inp.type3 is type3types.u32: if operator := OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'i32.{operator}') return if operator := U32_OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'i32.{operator}') return - if mtyp == 'u64': + if inp.type3 is type3types.u64: if operator := OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'i64.{operator}') return if operator := U64_OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'i64.{operator}') return - if mtyp == 'i32': + if inp.type3 is type3types.i32: if operator := OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'i32.{operator}') return if operator := I32_OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'i32.{operator}') return - if mtyp == 'i64': + if inp.type3 is type3types.i64: if operator := OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'i64.{operator}') return if operator := I64_OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'i64.{operator}') return - if mtyp == 'f32': + if inp.type3 is type3types.f32: if operator := OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'f32.{operator}') return if operator := F32_OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'f32.{operator}') return - if mtyp == 'f64': + if inp.type3 is type3types.f64: if operator := OPERATOR_MAP.get(inp.operator, None): wgn.add_statement(f'f64.{operator}') return @@ -278,19 +253,18 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: wgn.add_statement(f'f64.{operator}') return - raise NotImplementedError(expression, inp.type_var, inp.operator) + raise NotImplementedError(expression, inp.type3, inp.operator) if isinstance(inp, ourlang.UnaryOp): expression(wgn, inp.right) - assert inp.type_var is not None, typing.ASSERTION_ERROR - mtyp = typing.simplify(inp.type_var) + assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR - if mtyp == 'f32': + if inp.type3 is type3types.f32: if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS: wgn.add_statement(f'f32.{inp.operator}') return - if mtyp == 'f64': + if inp.type3 is type3types.f64: if inp.operator in ourlang.WEBASSEMBLY_BUILTIN_FLOAT_OPS: wgn.add_statement(f'f64.{inp.operator}') return @@ -307,7 +281,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: # # Nothing to do, you can use an u8 value as a u32 no problem # return - raise NotImplementedError(expression, inp.type_var, inp.operator) + raise NotImplementedError(expression, inp.type3, inp.operator) if isinstance(inp, ourlang.FunctionCall): for arg in inp.arguments: @@ -317,12 +291,12 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: return if isinstance(inp, ourlang.Subscript): - assert inp.varref.type3 is not None, typing.ASSERTION_ERROR - - assert inp.varref.type_var is not None, typing.ASSERTION_ERROR - tc_type = inp.varref.type_var.get_type() - if tc_type is None: - raise NotImplementedError(expression, inp, inp.varref.type_var) + # assert inp.varref.type3 is not None, typing.ASSERTION_ERROR + # + # assert inp.varref.type_var is not None, typing.ASSERTION_ERROR + # tc_type = inp.varref.type_var.get_type() + # if tc_type is None: + # raise NotImplementedError(expression, inp, inp.varref.type_var) # if tc_prim.primitive == typing.TypeConstraintPrimitive.Primitive.STATIC_ARRAY: # if not isinstance(inp.index, ourlang.ConstantPrimitive): @@ -362,7 +336,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None: # wgn.add_statement(f'{mtyp}.load', 'offset=' + str(bitwidth // 8 * inp.index.value)) # return - raise NotImplementedError(expression, inp, inp.varref.type_var) + raise NotImplementedError(expression, inp, inp.varref.type3) # TODO: Broken after new type system @@ -422,12 +396,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None: """ Compile: Fold expression """ - assert inp.base.type_var is not None, typing.ASSERTION_ERROR - mtyp = typing.simplify(inp.base.type_var) - if mtyp is None: - # In the future might extend this by having structs or tuples - # as members of struct or tuples - raise NotImplementedError(expression, inp, inp.base) + assert isinstance(inp.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR raise NotImplementedError('TODO: Broken after new type system') @@ -546,7 +515,7 @@ def function_argument(inp: ourlang.FunctionParam) -> wasm.Param: """ Compile: function argument """ - return (inp.name, type_var(inp.type_var), ) + return (inp.name, type3(inp.type3), ) def import_(inp: ourlang.Function) -> wasm.Import: """ @@ -562,7 +531,7 @@ def import_(inp: ourlang.Function) -> wasm.Import: function_argument(x) for x in inp.posonlyargs ], - type_var(inp.returns_type_var) + type3(inp.returns_type3) ) def function(inp: ourlang.Function) -> wasm.Function: @@ -592,7 +561,7 @@ def function(inp: ourlang.Function) -> wasm.Function: (k, v.wasm_type(), ) for k, v in wgn.locals.items() ], - type_var(inp.returns_type_var), + type3(inp.returns_type3), wgn.statements ) diff --git a/phasm/ourlang.py b/phasm/ourlang.py index 55d2bc2..359d38c 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -10,12 +10,6 @@ from typing_extensions import Final WEBASSEMBLY_BUILTIN_FLOAT_OPS: Final = ('abs', 'sqrt', 'ceil', 'floor', 'trunc', 'nearest', ) WEBASSEMBLY_BUILTIN_BYTES_OPS: Final = ('len', ) -from .typing import ( - TypeStruct, - - TypeVar, -) - from .type3 import types as type3types from .type3.types import Type3, Type3OrPlaceholder, PlaceholderForType @@ -23,15 +17,12 @@ class Expression: """ An expression within a statement """ - __slots__ = ('type3', 'type_var', ) + __slots__ = ('type3', ) type3: Type3OrPlaceholder - type_var: Optional[TypeVar] - def __init__(self) -> None: self.type3 = PlaceholderForType([self]) - self.type_var = None class Constant(Expression): """ @@ -213,24 +204,22 @@ class FunctionParam: """ A parameter for a Function """ - __slots__ = ('name', 'type3', 'type_str', 'type_var', ) + __slots__ = ('name', 'type3', 'type_str', ) name: str type3: Type3 type_str: str - type_var: Optional[TypeVar] def __init__(self, name: str, type3: Type3) -> None: self.name = name self.type3 = type3 self.type_str = type3.name - self.type_var = None class Function: """ A function processes input and produces output """ - __slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_type3', 'returns_str', 'returns_type_var', 'posonlyargs', ) + __slots__ = ('name', 'lineno', 'exported', 'imported', 'statements', 'returns_type3', 'returns_str', 'posonlyargs', ) name: str lineno: int @@ -239,7 +228,6 @@ class Function: statements: List[Statement] returns_type3: Type3 returns_str: str - returns_type_var: Optional[TypeVar] posonlyargs: List[FunctionParam] def __init__(self, name: str, lineno: int) -> None: @@ -250,7 +238,6 @@ class Function: self.statements = [] self.returns_type3 = type3types.none self.returns_str = 'None' - self.returns_type_var = None self.posonlyargs = [] # TODO: Broken after new type system @@ -299,13 +286,12 @@ class ModuleConstantDef: """ A constant definition within a module """ - __slots__ = ('name', 'lineno', 'type3', 'type_str', 'type_var', 'constant', 'data_block', ) + __slots__ = ('name', 'lineno', 'type3', 'type_str', 'constant', 'data_block', ) name: str lineno: int type3: Type3 type_str: str - type_var: Optional[TypeVar] constant: Constant data_block: Optional['ModuleDataBlock'] @@ -314,7 +300,6 @@ class ModuleConstantDef: self.lineno = lineno self.type3 = type3 self.type_str = type3.name - self.type_var = None self.constant = constant self.data_block = data_block @@ -349,12 +334,12 @@ class Module: __slots__ = ('data', 'types', 'structs', 'constant_defs', 'functions',) data: ModuleData - structs: Dict[str, TypeStruct] + # structs: Dict[str, TypeStruct] constant_defs: Dict[str, ModuleConstantDef] functions: Dict[str, Function] def __init__(self) -> None: self.data = ModuleData() - self.structs = {} + # self.structs = {} self.constant_defs = {} self.functions = {} diff --git a/phasm/parser.py b/phasm/parser.py index 641ad39..3f800c9 100644 --- a/phasm/parser.py +++ b/phasm/parser.py @@ -5,13 +5,6 @@ from typing import Any, Dict, NoReturn, Union import ast -from .typing import ( - BUILTIN_TYPES, - - TypeStruct, - TypeStructMember, -) - from .type3 import types as type3types from .exceptions import StaticError @@ -108,12 +101,12 @@ class OurVisitor: return module - def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, TypeStruct, ModuleConstantDef]: + def pre_visit_Module_stmt(self, module: Module, node: ast.stmt) -> Union[Function, ModuleConstantDef]: # TypeStruct if isinstance(node, ast.FunctionDef): return self.pre_visit_Module_FunctionDef(module, node) - if isinstance(node, ast.ClassDef): - return self.pre_visit_Module_ClassDef(module, node) + # if isinstance(node, ast.ClassDef): + # return self.pre_visit_Module_ClassDef(module, node) if isinstance(node, ast.AnnAssign): return self.pre_visit_Module_AnnAssign(module, node) @@ -162,35 +155,36 @@ class OurVisitor: return function - def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> TypeStruct: - struct = TypeStruct(node.name, node.lineno) - - _not_implemented(not node.bases, 'ClassDef.bases') - _not_implemented(not node.keywords, 'ClassDef.keywords') - _not_implemented(not node.decorator_list, 'ClassDef.decorator_list') - - offset = 0 - - for stmt in node.body: - if not isinstance(stmt, ast.AnnAssign): - raise NotImplementedError(f'Class with {stmt} nodes') - - if not isinstance(stmt.target, ast.Name): - raise NotImplementedError('Class with default values') - - if not stmt.value is None: - raise NotImplementedError('Class with default values') - - if stmt.simple != 1: - raise NotImplementedError('Class with non-simple arguments') - - raise NotImplementedError('TODO: Broken after new type system') - member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset) - - struct.members.append(member) - offset += member.type.alloc_size() - - return struct + def pre_visit_Module_ClassDef(self, module: Module, node: ast.ClassDef) -> None: # TypeStruct: + raise NotImplementedError + # struct = TypeStruct(node.name, node.lineno) + # + # _not_implemented(not node.bases, 'ClassDef.bases') + # _not_implemented(not node.keywords, 'ClassDef.keywords') + # _not_implemented(not node.decorator_list, 'ClassDef.decorator_list') + # + # offset = 0 + # + # for stmt in node.body: + # if not isinstance(stmt, ast.AnnAssign): + # raise NotImplementedError(f'Class with {stmt} nodes') + # + # if not isinstance(stmt.target, ast.Name): + # raise NotImplementedError('Class with default values') + # + # if not stmt.value is None: + # raise NotImplementedError('Class with default values') + # + # if stmt.simple != 1: + # raise NotImplementedError('Class with non-simple arguments') + # + # raise NotImplementedError('TODO: Broken after new type system') + # member = TypeStructMember(stmt.target.id, self.visit_type(module, stmt.annotation), offset) + # + # struct.members.append(member) + # offset += member.type.alloc_size() + # + # return struct def pre_visit_Module_AnnAssign(self, module: Module, node: ast.AnnAssign) -> ModuleConstantDef: if not isinstance(node.target, ast.Name): diff --git a/phasm/type3/types.py b/phasm/type3/types.py index 66b9a11..5430cdf 100644 --- a/phasm/type3/types.py +++ b/phasm/type3/types.py @@ -6,6 +6,8 @@ constraint generator works with. """ from typing import Any, Dict, Iterable, List, Protocol, Union +TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method' + class ExpressionProtocol(Protocol): """ A protocol for classes that should be updated on substitution diff --git a/phasm/typer.py b/phasm/typer.py deleted file mode 100644 index 78f6fce..0000000 --- a/phasm/typer.py +++ /dev/null @@ -1,193 +0,0 @@ -""" -Type checks and enriches the given ast -""" -from . import ourlang - -from .exceptions import TypingError -from .typing import ( - Context, - PhasmTypeInteger, PhasmTypeReal, - TypeConstraintBitWidth, TypeConstraintSigned, TypeConstraintSubscript, - TypeVar, - from_str, -) - -def phasm_type(inp: ourlang.Module) -> None: - module(inp) - -def constant(ctx: Context, inp: ourlang.Constant) -> TypeVar: - if isinstance(inp, ourlang.ConstantPrimitive): - result = ctx.new_var() - - if isinstance(inp.value, int): - result.set_type(PhasmTypeInteger) - - # Need at least this many bits to store this constant value - result.add_constraint(TypeConstraintBitWidth(minb=len(bin(inp.value)) - 2)) - # Don't dictate anything about signedness - you can use a signed - # constant in an unsigned variable if the bits fit - result.add_constraint(TypeConstraintSigned(None)) - - result.add_location(str(inp.value)) - - inp.type_var = result - - return result - - if isinstance(inp.value, float): - result.set_type(PhasmTypeReal) - - # We don't have fancy logic here to detect if the float constant - # fits in the given type. There a number of edge cases to consider, - # before implementing this. - - # 1) It may fit anyhow - # e.g., if the user has 3.14 as a float constant, neither a - # f32 nor a f64 can really fit this value. But does that mean - # we should throw an error? - - # If we'd implement it, we'd want to convert it to hex using - # inp.value.hex(), which would give us the mantissa and exponent. - # We can use those to determine what bit size the value should be in. - - # If that doesn't work out, we'd need another way to calculate the - # difference between what was written and what actually gets stored - # in memory, and warn if the difference is beyond a treshold. - - result.add_location(str(inp.value)) - - inp.type_var = result - - return result - - raise NotImplementedError(constant, inp, inp.value) - - if isinstance(inp, ourlang.ConstantTuple): - result = ctx.new_var() - - result.add_constraint(TypeConstraintSubscript(members=( - constant(ctx, x) - for x in inp.value - ))) - result.add_location(str(inp.value)) - - inp.type_var = result - - return result - - raise NotImplementedError(constant, inp) - -def expression(ctx: Context, inp: ourlang.Expression) -> 'TypeVar': - if isinstance(inp, ourlang.Constant): - return constant(ctx, inp) - - if isinstance(inp, ourlang.VariableReference): - assert inp.variable.type_var is not None - - inp.type_var = inp.variable.type_var - return inp.variable.type_var - - if isinstance(inp, ourlang.UnaryOp): - # TODO: Simplified version - if inp.operator not in ('sqrt', ): - raise NotImplementedError(expression, inp, inp.operator) - - right = expression(ctx, inp.right) - - inp.type_var = right - - return right - - if isinstance(inp, ourlang.BinaryOp): - if inp.operator in ('+', '-', '*', '/', '|', '&', '^'): - left = expression(ctx, inp.left) - right = expression(ctx, inp.right) - ctx.unify(left, right) - - inp.type_var = left - return left - - if inp.operator in ('<<', '>>', ): - inp.type_var = ctx.new_var(PhasmTypeInteger) - inp.type_var.add_constraint(TypeConstraintBitWidth(oneof=(32, 64, ))) - inp.type_var.add_constraint(TypeConstraintSigned(False)) - - left = expression(ctx, inp.left) - right = expression(ctx, inp.right) - ctx.unify(left, right) - - ctx.unify(inp.type_var, left) - - return left - - raise NotImplementedError(expression, inp, inp.operator) - - if isinstance(inp, ourlang.FunctionCall): - assert inp.function.returns_type_var is not None - - for param, expr in zip(inp.function.posonlyargs, inp.arguments): - assert param.type_var is not None - - arg = expression(ctx, expr) - ctx.unify(param.type_var, arg) - - return inp.function.returns_type_var - - if isinstance(inp, ourlang.Subscript): - if not isinstance(inp.index, ourlang.ConstantPrimitive): - raise NotImplementedError(expression, inp, inp.index) - if not isinstance(inp.index.value, int): - raise NotImplementedError(expression, inp, inp.index.value) - - expression(ctx, inp.varref) - assert inp.varref.type_var is not None - - # TODO: I'd much rather resolve this using the narrow functions - tc_subs = inp.varref.type_var.get_constraint(TypeConstraintSubscript) - if tc_subs is None: - raise TypingError(f'Type cannot be subscripted: {inp.varref.type_var}') from None - - try: - # TODO: I'd much rather resolve this using the narrow functions - member = tc_subs.members[inp.index.value] - except IndexError: - raise TypingError(f'Type cannot be subscripted with index {inp.index.value}: {inp.varref.type_var}') from None - - inp.type_var = member - return member - - raise NotImplementedError(expression, inp) - -def function(ctx: Context, inp: ourlang.Function) -> None: - if len(inp.statements) != 1 or not isinstance(inp.statements[0], ourlang.StatementReturn): - raise NotImplementedError('Functions with not just a return statement') - typ = expression(ctx, inp.statements[0].value) - - assert inp.returns_type_var is not None - ctx.unify(inp.returns_type_var, typ) - -def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> None: - constant(ctx, inp.constant) - - if inp.type_str is None: - inp.type_var = ctx.new_var() - else: - inp.type_var = from_str(ctx, inp.type_str) - - assert inp.constant.type_var is not None - # This doesn't work sufficiently with StaticArray - ctx.unify(inp.type_var, inp.constant.type_var) - -def module(inp: ourlang.Module) -> None: - ctx = Context() - - for func in inp.functions.values(): - func.returns_type_var = from_str(ctx, func.returns_str, f'{func.name}.(returns)') - for param in func.posonlyargs: - param.type_var = from_str(ctx, param.type_str, f'{func.name}.{param.name}') - - for cdef in inp.constant_defs.values(): - module_constant_def(ctx, cdef) - - for func in inp.functions.values(): - function(ctx, func) diff --git a/phasm/typing.py b/phasm/typing.py deleted file mode 100644 index c94c9a9..0000000 --- a/phasm/typing.py +++ /dev/null @@ -1,653 +0,0 @@ -""" -The phasm type system -""" -from typing import Any, Callable, Dict, Iterable, Optional, List, Set, Type, Union -from typing import TypeVar as MyPyTypeVar - -import enum -import re - -from .exceptions import TypingError - -class TypeBase: - """ - TypeBase base class - """ - __slots__ = () - - def alloc_size(self) -> int: - """ - When allocating this type in memory, how many bytes do we need to reserve? - """ - raise NotImplementedError(self, 'alloc_size') - -class TypeBytes(TypeBase): - """ - The bytes type - """ - __slots__ = () - -class TypeTupleMember: - """ - Represents a tuple member - """ - def __init__(self, idx: int, type_: TypeBase, offset: int) -> None: - self.idx = idx - self.type = type_ - self.offset = offset - -class TypeTuple(TypeBase): - """ - The tuple type - """ - __slots__ = ('members', ) - - members: List[TypeTupleMember] - - def __init__(self) -> None: - self.members = [] - - def render_internal_name(self) -> str: - """ - Generates an internal name for this tuple - """ - mems = '@'.join('?' for x in self.members) # FIXME: Should not be a questionmark - assert ' ' not in mems, 'Not implement yet: subtuples' - return f'tuple@{mems}' - - def alloc_size(self) -> int: - return sum( - x.type.alloc_size() - for x in self.members - ) - -class TypeStaticArrayMember: - """ - Represents a static array member - """ - def __init__(self, idx: int, offset: int) -> None: - self.idx = idx - self.offset = offset - -class TypeStaticArray(TypeBase): - """ - The static array type - """ - __slots__ = ('member_type', 'members', ) - - member_type: TypeBase - members: List[TypeStaticArrayMember] - - def __init__(self, member_type: TypeBase) -> None: - self.member_type = member_type - self.members = [] - - def alloc_size(self) -> int: - return self.member_type.alloc_size() * len(self.members) - -class TypeStructMember: - """ - Represents a struct member - """ - def __init__(self, name: str, type_: TypeBase, offset: int) -> None: - self.name = name - self.type = type_ - self.offset = offset - -class TypeStruct(TypeBase): - """ - A struct has named properties - """ - __slots__ = ('name', 'lineno', 'members', ) - - name: str - lineno: int - members: List[TypeStructMember] - - def __init__(self, name: str, lineno: int) -> None: - self.name = name - self.lineno = lineno - self.members = [] - - def get_member(self, name: str) -> Optional[TypeStructMember]: - """ - Returns a member by name - """ - for mem in self.members: - if mem.name == name: - return mem - - return None - - def alloc_size(self) -> int: - return sum( - x.type.alloc_size() - for x in self.members - ) - -## NEW STUFF BELOW - -# This error can also mean that the typer somewhere forgot to write a type -# back to the AST. If so, we need to fix the typer. -ASSERTION_ERROR = 'You must call phasm_type after calling phasm_parse before you can call any other method' - -class PhasmType: - __slots__ = ('name', 'args', 'arg_count', ) - - name: str - args: List[Union['PhasmType', 'TypeVar']] - arg_count: int - - def __init__(self, name: str, arg_count: int = 0) -> None: - self.name = name - self.args = [] - self.arg_count = arg_count - - def __call__(self, type_arg: Union['PhasmType', 'TypeVar']) -> 'PhasmType': - assert 0 < self.arg_count - - result = PhasmType(self.name, self.arg_count - 1) - result.args = self.args + [type_arg] - - return result - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, PhasmType): - raise NotImplementedError - - return ( - self.name == other.name - and self.args == other.args - and self.arg_count == other.arg_count - ) - - def __ne__(self, other: Any) -> bool: - if not isinstance(other, PhasmType): - raise NotImplementedError - - return ( - self.name != other.name - or self.args != other.args - or self.arg_count != other.arg_count - ) - - def __repr__(self) -> str: - return ( - 'PhasmType' + self.name + ' ' - + ' '.join(map(repr, self.args)) + ' ' - + ' '.join(['?'] * self.arg_count) - ).strip() - -PhasmTypeInteger = PhasmType('Integer') -PhasmTypeReal = PhasmType('Real') -PhasmTypeStaticArray = PhasmType('StaticArray', 1) - -class TypingNarrowProtoError(TypingError): - """ - A proto error when trying to narrow two types - - This gets turned into a TypingNarrowError by the unify method - """ - # FIXME: Use consistent naming for unify / narrow / entangle - -class TypingNarrowError(TypingError): - """ - An error when trying to unify two Type Variables - """ - def __init__(self, l: 'TypeVar', r: 'TypeVar', msg: str) -> None: - super().__init__( - f'Cannot narrow types {l} and {r}: {msg}' - ) - -class TypeConstraintBase: - """ - Base class for classes implementing a constraint on a type - """ - def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintBase': - raise NotImplementedError('narrow', self, other) - -class TypeConstraintSigned(TypeConstraintBase): - """ - Constraint on whether a signed value can be used or not, or whether - a value can be used in a signed expression - """ - __slots__ = ('signed', ) - - signed: Optional[bool] - - def __init__(self, signed: Optional[bool]) -> None: - self.signed = signed - - def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintSigned': - if not isinstance(other, TypeConstraintSigned): - raise Exception('Invalid comparison') - - if other.signed is None: - return TypeConstraintSigned(self.signed) - if self.signed is None: - return TypeConstraintSigned(other.signed) - - if self.signed is not other.signed: - raise TypingNarrowProtoError('Signed does not match') - - return TypeConstraintSigned(self.signed) - - def __repr__(self) -> str: - return f'Signed={self.signed}' - -class TypeConstraintBitWidth(TypeConstraintBase): - """ - Constraint on how many bits an expression has or can possibly have - """ - __slots__ = ('oneof', ) - - oneof: Set[int] - - def __init__(self, *, oneof: Optional[Iterable[int]] = None, minb: Optional[int] = None, maxb: Optional[int] = None) -> None: - # For now, support up to 64 bits values - self.oneof = set(oneof) if oneof is not None else set(range(1, 65)) - - if minb is not None: - self.oneof = { - x - for x in self.oneof - if minb <= x - } - - if maxb is not None: - self.oneof = { - x - for x in self.oneof - if x <= maxb - } - - def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintBitWidth': - if not isinstance(other, TypeConstraintBitWidth): - raise Exception('Invalid comparison') - - new_oneof = self.oneof & other.oneof - - if not new_oneof: - raise TypingNarrowProtoError('Memory width cannot be resolved') - - return TypeConstraintBitWidth(oneof=new_oneof) - - def __repr__(self) -> str: - result = 'BitWidth=' - - items = list(sorted(self.oneof)) - if not items: - return result - - while items: - itm = items.pop(0) - result += str(itm) - - cnt = 0 - while cnt < len(items) and items[cnt] == itm + cnt + 1: - cnt += 1 - - if cnt == 1: - result += ',' + str(items[0]) - elif cnt > 1: - result += '..' + str(items[cnt - 1]) - - items = items[cnt:] - if items: - result += ',' - - return result - -class TypeConstraintSubscript(TypeConstraintBase): - """ - Constraint on allowing a type to be subscripted - """ - __slots__ = ('members', ) - - members: List['TypeVar'] - - def __init__(self, *, members: Iterable['TypeVar']) -> None: - self.members = list(members) - - def narrow(self, ctx: 'Context', other: 'TypeConstraintBase') -> 'TypeConstraintSubscript': - if not isinstance(other, TypeConstraintSubscript): - raise Exception('Invalid comparison') - - if len(self.members) != len(other.members): - raise TypingNarrowProtoError('Member count does not match') - - newmembers = [] - for smb, omb in zip(self.members, other.members): - nmb = ctx.new_var() - ctx.unify(nmb, smb) - ctx.unify(nmb, omb) - newmembers.append(nmb) - - return TypeConstraintSubscript(members=newmembers) - - def __repr__(self) -> str: - return 'Subscript=(' + ','.join(map(repr, self.members)) + ')' - -TTypeConstraintClass = MyPyTypeVar('TTypeConstraintClass', bound=TypeConstraintBase) - -class TypeVar: - """ - A type variable - """ - # FIXME: Explain the type system - __slots__ = ('ctx', 'ctx_id', ) - - ctx: 'Context' - ctx_id: int - - def __init__(self, ctx: 'Context', ctx_id: int) -> None: - self.ctx = ctx - self.ctx_id = ctx_id - - def get_type(self) -> Optional[PhasmType]: - return self.ctx.var_types[self.ctx_id] - - def set_type(self, type_: PhasmType) -> None: - assert self.ctx.var_types[self.ctx_id] is None, 'Type already set' - self.ctx.var_types[self.ctx_id] = type_ - - def add_constraint(self, newconst: TypeConstraintBase) -> None: - csts = self.ctx.var_constraints[self.ctx_id] - - if newconst.__class__ in csts: - csts[newconst.__class__] = csts[newconst.__class__].narrow(self.ctx, newconst) - else: - csts[newconst.__class__] = newconst - - def get_constraint(self, const_type: Type[TTypeConstraintClass]) -> Optional[TTypeConstraintClass]: - csts = self.ctx.var_constraints[self.ctx_id] - - res = csts.get(const_type, None) - assert res is None or isinstance(res, const_type) # type hint - return res - - def add_location(self, ref: str) -> None: - self.ctx.var_locations[self.ctx_id].add(ref) - - def __eq__(self, other: Any) -> bool: - raise NotImplementedError - - def __ne__(self, other: Any) -> bool: - raise NotImplementedError - - def __repr__(self) -> str: - typ = self.ctx.var_types[self.ctx_id] - - return ( - 'TypeVar<' - + ('?' if typ is None else repr(typ)) - + '; ' - + '; '.join(map(repr, self.ctx.var_constraints[self.ctx_id].values())) - + '; locations: ' - + ', '.join(sorted(self.ctx.var_locations[self.ctx_id])) - + '>' - ) - -class Context: - """ - The context for a collection of type variables - """ - def __init__(self) -> None: - # Variables are unified (or entangled, if you will) - # that means that each TypeVar within a context has an ID, - # and all TypeVars with the same ID are the same TypeVar, - # even if they are a different instance - self.next_ctx_id = 1 - self.vars_by_id: Dict[int, List[TypeVar]] = {} - - # Store the TypeVar properties as a lookup - # so we can update these when unifying - self.var_types: Dict[int, Optional[PhasmType]] = {} - self.var_constraints: Dict[int, Dict[Type[TypeConstraintBase], TypeConstraintBase]] = {} - self.var_locations: Dict[int, Set[str]] = {} - - def new_var(self, type_: Optional[PhasmType] = None) -> TypeVar: - ctx_id = self.next_ctx_id - self.next_ctx_id += 1 - - result = TypeVar(self, ctx_id) - - self.vars_by_id[ctx_id] = [result] - self.var_types[ctx_id] = type_ - self.var_constraints[ctx_id] = {} - self.var_locations[ctx_id] = set() - - return result - - def unify(self, l: Optional[TypeVar], r: Optional[TypeVar]) -> None: - # FIXME: Write method doc, find out why pylint doesn't error - - assert l is not None, ASSERTION_ERROR - assert r is not None, ASSERTION_ERROR - - assert l.ctx_id != r.ctx_id # Dunno if this'll happen, if so, just return - - # Backup some values that we'll overwrite - l_ctx_id = l.ctx_id - r_ctx_id = r.ctx_id - l_type = self.var_types[l_ctx_id] - r_type = self.var_types[r_ctx_id] - l_r_var_list = self.vars_by_id[l_ctx_id] + self.vars_by_id[r_ctx_id] - - # Create a new TypeVar, with the combined constraints - # and locations of the old ones - n = self.new_var() - - if l_type is not None and r_type is not None and l_type != r_type: - raise TypingNarrowError(l, r, 'Type does not match') - self.var_types[n.ctx_id] = l_type - - try: - for const in self.var_constraints[l_ctx_id].values(): - n.add_constraint(const) - for const in self.var_constraints[r_ctx_id].values(): - n.add_constraint(const) - except TypingNarrowProtoError as exc: - raise TypingNarrowError(l, r, str(exc)) from None - - self.var_locations[n.ctx_id] = self.var_locations[l_ctx_id] | self.var_locations[r_ctx_id] - - # ## - # And unify (or entangle) the old ones - - # First update the IDs, so they all point to the new list - for type_var in l_r_var_list: - type_var.ctx_id = n.ctx_id - - # Update our registry of TypeVars by ID, so we can find them - # on the next unify - self.vars_by_id[n.ctx_id].extend(l_r_var_list) - - # Then delete the old values for the now gone variables - # Do this last, so exceptions thrown in the code above - # still have a valid context - del self.var_constraints[l_ctx_id] - del self.var_constraints[r_ctx_id] - del self.var_locations[l_ctx_id] - del self.var_locations[r_ctx_id] - -def simplify(inp: TypeVar) -> Optional[str]: - """ - Simplifies a TypeVar into a string that wasm can work with - and users can recognize - - Should round trip with from_str - """ - tc_bits = inp.get_constraint(TypeConstraintBitWidth) - tc_sign = inp.get_constraint(TypeConstraintSigned) - - if inp.get_type() is None: - return None - - if inp.get_type() is PhasmTypeInteger: - if tc_bits is None or tc_sign is None: - return None - - if tc_sign.signed is None or len(tc_bits.oneof) != 1: - return None - - bitwidth = next(iter(tc_bits.oneof)) - if bitwidth not in (8, 32, 64): - return None - - base = 'i' if tc_sign.signed else 'u' - return f'{base}{bitwidth}' - - if inp.get_type() is PhasmTypeReal: - if tc_bits is None or tc_sign is not None: # Floats should not hava sign constraint - return None - - if len(tc_bits.oneof) != 1: - return None - - bitwidth = next(iter(tc_bits.oneof)) - if bitwidth not in (32, 64): - return None - - return f'f{bitwidth}' - - # if primitive is TypeConstraintPrimitive.Primitive.STATIC_ARRAY: - # tc_subs = inp.get_constraint(TypeConstraintSubscript) - # assert tc_subs is not None - # assert tc_subs.members - # - # sab = simplify(tc_subs.members[0]) - # if sab is None: - # return None - # - # return f'{sab}[{len(tc_subs.members)}]' - - return None - -def make_u8(ctx: Context) -> TypeVar: - """ - Makes a u8 TypeVar - """ - result = ctx.new_var(PhasmTypeInteger) - result.add_constraint(TypeConstraintBitWidth(minb=8, maxb=8)) - result.add_constraint(TypeConstraintSigned(False)) - result.add_location('u8') - return result - -def make_u32(ctx: Context) -> TypeVar: - """ - Makes a u32 TypeVar - """ - result = ctx.new_var(PhasmTypeInteger) - result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32)) - result.add_constraint(TypeConstraintSigned(False)) - result.add_location('u32') - return result - -def make_u64(ctx: Context) -> TypeVar: - """ - Makes a u64 TypeVar - """ - result = ctx.new_var(PhasmTypeInteger) - result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64)) - result.add_constraint(TypeConstraintSigned(False)) - result.add_location('u64') - return result - -def make_i32(ctx: Context) -> TypeVar: - """ - Makes a i32 TypeVar - """ - result = ctx.new_var(PhasmTypeInteger) - result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32)) - result.add_constraint(TypeConstraintSigned(True)) - result.add_location('i32') - return result - -def make_i64(ctx: Context) -> TypeVar: - """ - Makes a i64 TypeVar - """ - result = ctx.new_var(PhasmTypeInteger) - result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64)) - result.add_constraint(TypeConstraintSigned(True)) - result.add_location('i64') - return result - -def make_f32(ctx: Context) -> TypeVar: - """ - Makes a f32 TypeVar - """ - result = ctx.new_var(PhasmTypeReal) - result.add_constraint(TypeConstraintBitWidth(minb=32, maxb=32)) - result.add_location('f32') - return result - -def make_f64(ctx: Context) -> TypeVar: - """ - Makes a f64 TypeVar - """ - result = ctx.new_var(PhasmTypeReal) - result.add_constraint(TypeConstraintBitWidth(minb=64, maxb=64)) - result.add_location('f64') - return result - -BUILTIN_TYPES: Dict[str, Callable[[Context], TypeVar]] = { - 'u8': make_u8, - 'u32': make_u32, - 'u64': make_u64, - 'i32': make_i32, - 'i64': make_i64, - 'f32': make_f32, - 'f64': make_f64, -} - -TYPE_MATCH_STATIC_ARRAY = re.compile(r'^([uif][0-9]+)\[([0-9]+)\]') - -def from_str(ctx: Context, inp: str, location: Optional[str] = None) -> TypeVar: - """ - Creates a new TypeVar from the string - - Should round trip with simplify - - The location is a reference to where you found the string - in the source code. - - This could be conidered part of parsing. Though that would give trouble - with the context creation. - """ - if inp in BUILTIN_TYPES: - result = BUILTIN_TYPES[inp](ctx) - if location is not None: - result.add_location(location) - return result - - match = TYPE_MATCH_STATIC_ARRAY.fullmatch(inp) - if match: - result = ctx.new_var(PhasmTypeStaticArray) - - result.add_constraint(TypeConstraintSubscript(members=( - # Make copies so they don't get entangled - # with each other. - from_str(ctx, match[1], match[1]) - for _ in range(int(match[2])) - ))) - - result.add_location(inp) - - if location is not None: - result.add_location(location) - - return result - - if inp.startswith('static_array ('): - # Temp workaround while both type2 and type3 are running simultaneous - result = ctx.new_var(PhasmTypeStaticArray) - result.add_location(inp) - - if location is not None: - result.add_location(location) - - return result - - raise NotImplementedError(from_str, inp) diff --git a/tests/integration/runners.py b/tests/integration/runners.py index 17c4d17..2f437ae 100644 --- a/tests/integration/runners.py +++ b/tests/integration/runners.py @@ -13,7 +13,6 @@ import wasmtime from phasm.compiler import phasm_compile from phasm.parser import phasm_parse -from phasm.typer import phasm_type from phasm.type3.entry import phasm_type3 from phasm import ourlang from phasm import wasm @@ -43,7 +42,6 @@ class RunnerBase: """ self.phasm_ast = phasm_parse(self.phasm_code) phasm_type3(self.phasm_ast) - phasm_type(self.phasm_ast) def compile_ast(self) -> None: """ diff --git a/tests/integration/test_code/__init__.py b/tests/integration/test_code/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/integration/test_code/test_typing.py b/tests/integration/test_code/test_typing.py deleted file mode 100644 index 7be1140..0000000 --- a/tests/integration/test_code/test_typing.py +++ /dev/null @@ -1,20 +0,0 @@ -import pytest - -from phasm import typing as sut - -class TestTypeConstraintBitWidth: - @pytest.mark.parametrize('oneof,exp', [ - (set(), '', ), - ({1}, '1', ), - ({1,2}, '1,2', ), - ({1,2,3}, '1..3', ), - ({1,2,3,4}, '1..4', ), - - ({1,3}, '1,3', ), - ({1,4}, '1,4', ), - - ({1,2,3,4,6,7,8,9}, '1..4,6..9', ), - ]) - def test_repr(self, oneof, exp): - mut_self = sut.TypeConstraintBitWidth(oneof=oneof) - assert ('BitWidth=' + exp) == repr(mut_self) diff --git a/tests/integration/test_lang/test_primitives.py b/tests/integration/test_lang/test_primitives.py index fc94897..b01acc0 100644 --- a/tests/integration/test_lang/test_primitives.py +++ b/tests/integration/test_lang/test_primitives.py @@ -1,6 +1,7 @@ import pytest from phasm.exceptions import TypingError +from phasm.type3.entry import Type3Exception from ..helpers import Suite from ..constants import ALL_INT_TYPES, ALL_FLOAT_TYPES, COMPLETE_INT_TYPES, COMPLETE_NUMERIC_TYPES, TYPE_MAP @@ -34,14 +35,14 @@ def testEntry() -> {type_}: assert TYPE_MAP[type_] == type(result.returned_value) @pytest.mark.integration_test -def test_expr_constant_entanglement(): +def test_expr_constant_literal_does_not_fit(): code_py = """ @exported def testEntry() -> u8: return 1000 """ - with pytest.raises(TypingError, match='u8.*1000'): + with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'): Suite(code_py).run_code() @pytest.mark.integration_test @@ -354,12 +355,12 @@ def testEntry() -> i32: @pytest.mark.integration_test def test_call_pre_defined(): code_py = """ -def helper(left: i32, right: i32) -> i32: - return left + right +def helper(left: i32) -> i32: + return left @exported def testEntry() -> i32: - return helper(10, 3) + return helper(13) """ result = Suite(code_py).run_code() @@ -416,7 +417,7 @@ def helper(left: {type_}, right: {type_}) -> {type_}: assert TYPE_MAP[type_] == type(result.returned_value) @pytest.mark.integration_test -def test_call_invalid_type(): +def test_call_invalid_return_type(): code_py = """ def helper() -> i64: return 19 @@ -426,5 +427,19 @@ def testEntry() -> i32: return helper() """ - with pytest.raises(TypingError, match=r'i32.*i64'): + with pytest.raises(Type3Exception, match=r'i32.*i64'): + Suite(code_py).run_code() + +@pytest.mark.integration_test +def test_call_invalid_arg_type(): + code_py = """ +def helper(left: u8) -> u8: + return left + +@exported +def testEntry() -> u8: + return helper(500) +""" + + with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'): Suite(code_py).run_code() diff --git a/tests/integration/test_lang/test_static_array.py b/tests/integration/test_lang/test_static_array.py index 912afd3..b83a2bf 100644 --- a/tests/integration/test_lang/test_static_array.py +++ b/tests/integration/test_lang/test_static_array.py @@ -15,7 +15,7 @@ CONSTANT: {type_}[3] = (24, 57, 80, ) @exported def testEntry() -> {type_}: - return CONSTANT[0] + return CONSTANT[1] """ result = Suite(code_py).run_code()