Compare commits

...

3 Commits

Author SHA1 Message Date
Johan B.W. de Vries
23ca1799b2 Implements sum for Foldable types
Foldable take a TypeConstructor. The first argument must be a
NatNum.
2025-05-07 19:14:29 +02:00
Johan B.W. de Vries
6b66935c67 Chore: Cleanup type checks
A lot of isinstance checks no longer did anything, since
the referred to variable was always a type.

In some places, we used it to check if a type was internal,
it's unclear if we will need to rebuild those checks in
the future.
2025-05-07 19:07:55 +02:00
Johan B.W. de Vries
d9a08cf0f7 Chore: Placeholders are now internal
They were exposed on AST, causing confusion.
Now they're only used in constraints.
2025-05-07 19:07:51 +02:00
17 changed files with 319 additions and 136 deletions

View File

@ -12,9 +12,12 @@
- Also, check the codes for FIXME and TODO - Also, check the codes for FIXME and TODO
- Allocation is done using pointers for members, is this desired? - Allocation is done using pointers for members, is this desired?
- See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental - See if we want to replace Fractional with Real, and add Rational, Irrationl, Algebraic, Transendental
- Implement q32? q64? Two i32/i64 divided?
- Does Subscript do what we want? It's a language feature rather a normal typed thing. How would you implement your own Subscript-able type? - Does Subscript do what we want? It's a language feature rather a normal typed thing. How would you implement your own Subscript-able type?
- Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types. - Clean up Subscript implementation - it's half implemented in the compiler. Makes more sense to move more parts to stdlib_types.
- Have a set of rules or guidelines for the constraint comments, they're messy. - Have a set of rules or guidelines for the constraint comments, they're messy.
- Do we need to store the placeholders on the expressions? They're only temporary while the type checker is running
- Might not even need to store them at all outside the generated constraints?
- Parser is putting stuff in ModuleDataBlock - Parser is putting stuff in ModuleDataBlock
- Surely the compiler should build data blocks - Surely the compiler should build data blocks

View File

@ -6,7 +6,6 @@ It's intented to be a "any color, as long as it's black" kind of renderer
from typing import Generator from typing import Generator
from . import ourlang, prelude from . import ourlang, prelude
from .type3.placeholders import TYPE3_ASSERTION_ERROR, Type3OrPlaceholder
from .type3.types import Type3 from .type3.types import Type3
@ -18,12 +17,10 @@ def phasm_render(inp: ourlang.Module) -> str:
Statements = Generator[str, None, None] Statements = Generator[str, None, None]
def type3(inp: Type3OrPlaceholder) -> str: def type3(inp: Type3) -> str:
""" """
Render: type's name Render: type's name
""" """
assert isinstance(inp, Type3), TYPE3_ASSERTION_ERROR
if inp is prelude.none: if inp is prelude.none:
return 'None' return 'None'

View File

@ -2,18 +2,19 @@
This module contains the code to convert parsed Ourlang into WebAssembly code This module contains the code to convert parsed Ourlang into WebAssembly code
""" """
import struct import struct
from typing import Dict, List, Optional from typing import Dict, List, Optional, Union
from . import codestyle, ourlang, prelude, wasm from . import codestyle, ourlang, prelude, wasm
from .runtime import calculate_alloc_size, calculate_member_offset from .runtime import calculate_alloc_size, calculate_member_offset
from .stdlib import alloc as stdlib_alloc from .stdlib import alloc as stdlib_alloc
from .stdlib import types as stdlib_types from .stdlib import types as stdlib_types
from .type3 import functions as type3functions from .type3 import functions as type3functions
from .type3 import placeholders as type3placeholders
from .type3 import typeclasses as type3classes from .type3 import typeclasses as type3classes
from .type3 import types as type3types from .type3 import types as type3types
from .wasmgenerator import Generator as WasmGenerator from .wasmgenerator import Generator as WasmGenerator
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before your program can be compiled'
LOAD_STORE_TYPE_MAP = { LOAD_STORE_TYPE_MAP = {
'i8': 'i32', # Have to use an u32, since there is no native i8 type 'i8': 'i32', # Have to use an u32, since there is no native i8 type
'u8': 'i32', # Have to use an u32, since there is no native u8 type 'u8': 'i32', # Have to use an u32, since there is no native u8 type
@ -259,6 +260,10 @@ INSTANCES = {
prelude.Promotable.methods['demote']: { prelude.Promotable.methods['demote']: {
'a=f32,b=f64': stdlib_types.f32_f64_demote, 'a=f32,b=f64': stdlib_types.f32_f64_demote,
}, },
prelude.Foldable.methods['sum']: {
'a=i32,t=i32[4]': stdlib_types.static_array_i32_4_sum,
'a=i32,t=i32[5]': stdlib_types.static_array_i32_5_sum,
},
} }
def phasm_compile(inp: ourlang.Module) -> wasm.Module: def phasm_compile(inp: ourlang.Module) -> wasm.Module:
@ -268,14 +273,14 @@ def phasm_compile(inp: ourlang.Module) -> wasm.Module:
""" """
return module(inp) return module(inp)
def type3(inp: type3placeholders.Type3OrPlaceholder) -> wasm.WasmType: def type3(inp: type3types.Type3) -> wasm.WasmType:
""" """
Compile: type Compile: type
Types are used for example in WebAssembly function parameters Types are used for example in WebAssembly function parameters
and return types. and return types.
""" """
assert isinstance(inp, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert inp is not None, TYPE3_ASSERTION_ERROR
if inp == prelude.none: if inp == prelude.none:
return wasm.WasmTypeNone() return wasm.WasmTypeNone()
@ -327,7 +332,7 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
""" """
Compile: Instantiation (allocation) of a tuple Compile: Instantiation (allocation) of a tuple
""" """
assert isinstance(inp.type3, type3types.Type3) assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
args: list[type3types.Type3] = [] args: list[type3types.Type3] = []
@ -345,7 +350,7 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
comment_elements = '' comment_elements = ''
for element in inp.elements: for element in inp.elements:
assert isinstance(element.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert element.type3 is not None, TYPE3_ASSERTION_ERROR
comment_elements += f'{element.type3.name}, ' comment_elements += f'{element.type3.name}, '
tmp_var = wgn.temp_var_i32('tuple_adr') tmp_var = wgn.temp_var_i32('tuple_adr')
@ -359,17 +364,11 @@ def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) ->
# Store each element individually # Store each element individually
offset = 0 offset = 0
for element, exp_type3 in zip(inp.elements, args, strict=True): for element, exp_type3 in zip(inp.elements, args, strict=True):
if isinstance(exp_type3, type3placeholders.PlaceholderForType):
assert exp_type3.resolve_as is not None
assert isinstance(exp_type3.resolve_as, type3types.Type3)
exp_type3 = exp_type3.resolve_as
assert element.type3 == exp_type3 assert element.type3 == exp_type3
if (prelude.InternalPassAsPointer, (exp_type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if (prelude.InternalPassAsPointer, (exp_type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
mtyp = 'i32' mtyp = 'i32'
else: else:
assert isinstance(exp_type3, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name] mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name]
wgn.add_statement('nop', comment='PRE') wgn.add_statement('nop', comment='PRE')
@ -392,7 +391,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
raise Exception raise Exception
if isinstance(inp, ourlang.ConstantPrimitive): if isinstance(inp, ourlang.ConstantPrimitive):
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
if inp.type3 in (prelude.i8, prelude.u8, ): if inp.type3 in (prelude.i8, prelude.u8, ):
# No native u8 type - treat as i32, with caution # No native u8 type - treat as i32, with caution
@ -433,7 +432,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
return return
if isinstance(inp.variable, ourlang.ModuleConstantDef): if isinstance(inp.variable, ourlang.ModuleConstantDef):
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
if (prelude.InternalPassAsPointer, (inp.type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if (prelude.InternalPassAsPointer, (inp.type3, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, )) assert isinstance(inp.variable.constant, (ourlang.ConstantBytes, ourlang.ConstantStruct, ourlang.ConstantTuple, ))
@ -443,28 +442,25 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.i32.const(address) wgn.i32.const(address)
return return
if isinstance(inp.type3, type3types.Type3):
expression(wgn, inp.variable.constant) expression(wgn, inp.variable.constant)
return return
raise NotImplementedError(expression, inp)
raise NotImplementedError(expression, inp.variable) raise NotImplementedError(expression, inp.variable)
if isinstance(inp, ourlang.BinaryOp): if isinstance(inp, ourlang.BinaryOp):
expression(wgn, inp.left) expression(wgn, inp.left)
expression(wgn, inp.right) expression(wgn, inp.right)
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
type_var_map: Dict[type3functions.TypeVariable, type3types.Type3] = {} type_var_map: Dict[Union[type3functions.TypeVariable, type3functions.TypeConstructorVariable], type3types.Type3] = {}
for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True): for type_var, arg_expr in zip(inp.operator.signature.args, [inp.left, inp.right, inp], strict=True):
if not isinstance(type_var, type3functions.TypeVariable): if not isinstance(type_var, type3functions.TypeVariable):
# Fixed type, not part of the lookup requirements # Fixed type, not part of the lookup requirements
continue continue
assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
type_var_map[type_var] = arg_expr.type3 type_var_map[type_var] = arg_expr.type3
instance_key = ','.join( instance_key = ','.join(
@ -488,12 +484,16 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
type_var_map = {} type_var_map = {}
for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True): for type_var, arg_expr in zip(inp.function.signature.args, inp.arguments + [inp], strict=True):
if not isinstance(type_var, type3functions.TypeVariable): if isinstance(type_var, type3types.Type3):
# Fixed type, not part of the lookup requirements # Fixed type, not part of the lookup requirements
continue continue
assert isinstance(arg_expr.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR if isinstance(type_var, (type3functions.TypeVariable, type3functions.TypeConstructorVariable, )):
assert arg_expr.type3 is not None, TYPE3_ASSERTION_ERROR
type_var_map[type_var] = arg_expr.type3 type_var_map[type_var] = arg_expr.type3
continue
raise NotImplementedError
instance_key = ','.join( instance_key = ','.join(
f'{k.letter}={v.name}' f'{k.letter}={v.name}'
@ -515,7 +515,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
return return
if isinstance(inp, ourlang.Subscript): if isinstance(inp, ourlang.Subscript):
assert isinstance(inp.varref.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR
if inp.varref.type3 is prelude.bytes_: if inp.varref.type3 is prelude.bytes_:
expression(wgn, inp.varref) expression(wgn, inp.varref)
@ -523,7 +523,7 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.call(stdlib_types.__subscript_bytes__) wgn.call(stdlib_types.__subscript_bytes__)
return return
assert isinstance(inp.varref.type3, type3types.Type3) assert inp.varref.type3 is not None, TYPE3_ASSERTION_ERROR
sa_args = prelude.static_array.did_construct(inp.varref.type3) sa_args = prelude.static_array.did_construct(inp.varref.type3)
if sa_args is not None: if sa_args is not None:
@ -549,7 +549,6 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
wgn.i32.mul() wgn.i32.mul()
wgn.i32.add() wgn.i32.add()
assert isinstance(el_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[el_type.name] mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load') wgn.add_statement(f'{mtyp}.load')
@ -562,18 +561,17 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
offset = 0 offset = 0
for el_type in tp_args[0:inp.index.value]: for el_type in tp_args[0:inp.index.value]:
assert isinstance(el_type, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert el_type is not None, TYPE3_ASSERTION_ERROR
offset += calculate_alloc_size(el_type) offset += calculate_alloc_size(el_type)
el_type = tp_args[inp.index.value] el_type = tp_args[inp.index.value]
assert isinstance(el_type, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert el_type is not None, TYPE3_ASSERTION_ERROR
expression(wgn, inp.varref) expression(wgn, inp.varref)
if (prelude.InternalPassAsPointer, (el_type, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: if (prelude.InternalPassAsPointer, (el_type, )) in prelude.PRELUDE_TYPE_CLASS_INSTANCES_EXISTING:
mtyp = 'i32' mtyp = 'i32'
else: else:
assert isinstance(el_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[el_type.name] mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
wgn.add_statement(f'{mtyp}.load', f'offset={offset}') wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
@ -582,14 +580,13 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
raise NotImplementedError(expression, inp, inp.varref.type3) raise NotImplementedError(expression, inp, inp.varref.type3)
if isinstance(inp, ourlang.AccessStructMember): if isinstance(inp, ourlang.AccessStructMember):
assert isinstance(inp.struct_type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert inp.struct_type3 is not None, TYPE3_ASSERTION_ERROR
st_args = prelude.struct.did_construct(inp.struct_type3) st_args = prelude.struct.did_construct(inp.struct_type3)
assert st_args is not None assert st_args is not None
member_type = st_args[inp.member] member_type = st_args[inp.member]
assert isinstance(member_type, type3types.Type3), NotImplementedError('Tuple of applied types / structs')
mtyp = LOAD_STORE_TYPE_MAP[member_type.name] mtyp = LOAD_STORE_TYPE_MAP[member_type.name]
expression(wgn, inp.varref) expression(wgn, inp.varref)
@ -608,7 +605,7 @@ def expression_fold(wgn: WasmGenerator, inp: ourlang.Fold) -> None:
""" """
Compile: Fold expression Compile: Fold expression
""" """
assert isinstance(inp.type3, type3types.Type3), type3placeholders.TYPE3_ASSERTION_ERROR assert inp.type3 is not None, TYPE3_ASSERTION_ERROR
if inp.iter.type3 is not prelude.bytes_: if inp.iter.type3 is not prelude.bytes_:
raise NotImplementedError(expression_fold, inp, inp.iter.type3) raise NotImplementedError(expression_fold, inp, inp.iter.type3)
@ -839,7 +836,7 @@ def module_data(inp: ourlang.ModuleData) -> bytes:
data_list: List[bytes] = [] data_list: List[bytes] = []
for constant in block.data: for constant in block.data:
assert isinstance(constant.type3, type3types.Type3), (id(constant), type3placeholders.TYPE3_ASSERTION_ERROR) assert constant.type3 is not None, TYPE3_ASSERTION_ERROR
if isinstance(constant, ourlang.ConstantMemoryStored) and block is not constant.data_block: if isinstance(constant, ourlang.ConstantMemoryStored) and block is not constant.data_block:
# It's stored in a different block # It's stored in a different block
@ -960,6 +957,7 @@ def module(inp: ourlang.Module) -> wasm.Module:
stdlib_types.__u32_pow2__, stdlib_types.__u32_pow2__,
stdlib_types.__u8_rotl__, stdlib_types.__u8_rotl__,
stdlib_types.__u8_rotr__, stdlib_types.__u8_rotr__,
stdlib_types.__sa_i32_sum__,
] + [ ] + [
function(x) function(x)
for x in inp.functions.values() for x in inp.functions.values()

View File

@ -6,7 +6,6 @@ from typing import Dict, Iterable, List, Optional, Union
from . import prelude from . import prelude
from .type3.functions import FunctionSignature, TypeVariableContext from .type3.functions import FunctionSignature, TypeVariableContext
from .type3.placeholders import PlaceholderForType, Type3OrPlaceholder
from .type3.typeclasses import Type3ClassMethod from .type3.typeclasses import Type3ClassMethod
from .type3.types import Type3 from .type3.types import Type3
@ -17,10 +16,10 @@ class Expression:
""" """
__slots__ = ('type3', ) __slots__ = ('type3', )
type3: Type3OrPlaceholder type3: Type3 | None
def __init__(self) -> None: def __init__(self) -> None:
self.type3 = PlaceholderForType([self]) self.type3 = None
class Constant(Expression): class Constant(Expression):
""" """
@ -198,10 +197,10 @@ class AccessStructMember(Expression):
__slots__ = ('varref', 'struct_type3', 'member', ) __slots__ = ('varref', 'struct_type3', 'member', )
varref: VariableReference varref: VariableReference
struct_type3: Type3OrPlaceholder struct_type3: Type3
member: str member: str
def __init__(self, varref: VariableReference, struct_type3: Type3OrPlaceholder, member: str) -> None: def __init__(self, varref: VariableReference, struct_type3: Type3, member: str) -> None:
super().__init__() super().__init__()
self.varref = varref self.varref = varref
@ -284,7 +283,7 @@ class FunctionParam:
__slots__ = ('name', 'type3', ) __slots__ = ('name', 'type3', )
name: str name: str
type3: Type3OrPlaceholder type3: Type3
def __init__(self, name: str, type3: Type3) -> None: def __init__(self, name: str, type3: Type3) -> None:
self.name = name self.name = name

View File

@ -163,8 +163,8 @@ class OurVisitor:
arg_type = self.visit_type(module, arg.annotation) arg_type = self.visit_type(module, arg.annotation)
# if isisntance(arg_type, TypeVariable): # FIXME: Allow TypeVariable in the function signature
# arg_type = PlaceHolderForType() # This would also require FunctionParam to accept a placeholder
function.signature.args.append(arg_type) function.signature.args.append(arg_type)
function.posonlyargs.append(FunctionParam( function.posonlyargs.append(FunctionParam(

View File

@ -1,20 +1,27 @@
""" """
The prelude are all the builtin types, type classes and methods The prelude are all the builtin types, type classes and methods
""" """
from typing import Any, Union
from ..type3.functions import TypeVariable from ..type3.functions import (
Constraint_TypeClassInstanceExists,
TypeConstructorVariable,
TypeVariable,
)
from ..type3.typeclasses import Type3Class from ..type3.typeclasses import Type3Class
from ..type3.types import ( from ..type3.types import (
IntType3,
Type3, Type3,
TypeConstructor,
TypeConstructor_StaticArray, TypeConstructor_StaticArray,
TypeConstructor_Struct, TypeConstructor_Struct,
TypeConstructor_Tuple, TypeConstructor_Tuple,
) )
PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Type3, ...]]] = set() PRELUDE_TYPE_CLASS_INSTANCES_EXISTING: set[tuple[Type3Class, tuple[Union[Type3, TypeConstructor[Any], TypeConstructor_Struct], ...]]] = set()
def instance_type_class(cls: Type3Class, *typ: Type3) -> None: def instance_type_class(cls: Type3Class, *typ: Union[Type3, TypeConstructor[Any], TypeConstructor_Struct]) -> None:
global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING global PRELUDE_TYPE_CLASS_INSTANCES_EXISTING
# TODO: Check for required existing instantiations # TODO: Check for required existing instantiations
@ -93,7 +100,7 @@ bytes_ = Type3('bytes')
This is a runtime-determined length piece of memory that can be indexed at runtime. This is a runtime-determined length piece of memory that can be indexed at runtime.
""" """
def sa_on_create(typ: Type3) -> None: def sa_on_create(args: tuple[Type3, IntType3], typ: Type3) -> None:
instance_type_class(InternalPassAsPointer, typ) instance_type_class(InternalPassAsPointer, typ)
static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create) static_array = TypeConstructor_StaticArray('static_array', on_create=sa_on_create)
@ -106,7 +113,7 @@ It should be applied with one argument. It has a runtime-dynamic length
of the same type repeated. of the same type repeated.
""" """
def tp_on_create(typ: Type3) -> None: def tp_on_create(args: tuple[Type3, ...], typ: Type3) -> None:
instance_type_class(InternalPassAsPointer, typ) instance_type_class(InternalPassAsPointer, typ)
tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create) tuple_ = TypeConstructor_Tuple('tuple', on_create=tp_on_create)
@ -143,6 +150,7 @@ PRELUDE_TYPES: dict[str, Type3] = {
a = TypeVariable('a') a = TypeVariable('a')
b = TypeVariable('b') b = TypeVariable('b')
t = TypeConstructorVariable('t', [])
InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={}) InternalPassAsPointer = Type3Class('InternalPassAsPointer', [a], methods={}, operators={})
""" """
@ -286,6 +294,14 @@ Promotable = Type3Class('Promotable', [a, b], methods={
instance_type_class(Promotable, f32, f64) instance_type_class(Promotable, f32, f64)
Foldable = Type3Class('Foldable', [t], methods={
'sum': [t(a), a],
}, operators={}, additional_context={
'sum': [Constraint_TypeClassInstanceExists(NatNum, [a])],
})
instance_type_class(Foldable, static_array)
PRELUDE_TYPE_CLASSES = { PRELUDE_TYPE_CLASSES = {
'Eq': Eq, 'Eq': Eq,
'Ord': Ord, 'Ord': Ord,
@ -321,4 +337,5 @@ PRELUDE_METHODS = {
**Sized_.methods, **Sized_.methods,
**Extendable.methods, **Extendable.methods,
**Promotable.methods, **Promotable.methods,
**Foldable.methods,
} }

View File

@ -384,6 +384,50 @@ def __u8_rotr__(g: Generator, x: i32, r: i32) -> i32:
return i32('return') # To satisfy mypy return i32('return') # To satisfy mypy
@func_wrapper()
def __sa_i32_sum__(g: Generator, adr: i32, arlen: i32) -> i32:
i32_size = 4
s = i32('s')
stop = i32('stop')
# stop = adr + ar_len * i32_size
g.local.get(adr)
g.local.get(arlen)
g.i32.const(i32_size)
g.i32.mul()
g.i32.add()
g.local.set(stop)
# sum = 0
g.i32.const(0)
g.local.set(s)
with g.loop():
# sum = sum + *adr
g.local.get(adr)
g.i32.load()
g.local.get(s)
g.i32.add()
g.local.set(s)
# adr = adr + i32_size
g.local.get(adr)
g.i32.const(i32_size)
g.i32.add()
g.local.tee(adr)
# loop if adr < stop
g.local.get(stop)
g.i32.lt_u()
g.br_if(0)
# return sum
g.local.get(s)
g.return_()
return i32('return') # To satisfy mypy
## ### ## ###
## class Eq ## class Eq
@ -920,3 +964,11 @@ def f32_f64_promote(g: Generator) -> None:
def f32_f64_demote(g: Generator) -> None: def f32_f64_demote(g: Generator) -> None:
g.f32.demote_f64() g.f32.demote_f64()
def static_array_i32_4_sum(g: Generator) -> None:
g.i32.const(4)
g.add_statement('call $stdlib.types.__sa_i32_sum__')
def static_array_i32_5_sum(g: Generator) -> None:
g.i32.const(5)
g.add_statement('call $stdlib.types.__sa_i32_sum__')

View File

@ -3,10 +3,11 @@ This module contains possible constraints generated based on the AST
These need to be resolved before the program can be compiled. These need to be resolved before the program can be compiled.
""" """
from typing import Dict, List, Optional, Tuple, Union from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from .. import ourlang, prelude from .. import ourlang, prelude
from . import placeholders, typeclasses, types from . import placeholders, typeclasses, types
from .placeholders import PlaceholderForType
class Error: class Error:
@ -48,7 +49,7 @@ class Context:
__slots__ = ('type_class_instances_existing', ) __slots__ = ('type_class_instances_existing', )
# Constraint_TypeClassInstanceExists # Constraint_TypeClassInstanceExists
type_class_instances_existing: set[tuple[typeclasses.Type3Class, tuple[types.Type3, ...]]] type_class_instances_existing: set[tuple[typeclasses.Type3Class, tuple[Union[types.Type3, types.TypeConstructor[Any], types.TypeConstructor_Struct], ...]]]
def __init__(self) -> None: def __init__(self) -> None:
self.type_class_instances_existing = set() self.type_class_instances_existing = set()
@ -157,7 +158,7 @@ class SameTypeConstraint(ConstraintBase):
return f'SameTypeConstraint({args}, comment={repr(self.comment)})' return f'SameTypeConstraint({args}, comment={repr(self.comment)})'
class TupleMatchConstraint(ConstraintBase): class TupleMatchConstraint(ConstraintBase):
def __init__(self, exp_type: placeholders.Type3OrPlaceholder, args: List[placeholders.Type3OrPlaceholder], comment: str): def __init__(self, exp_type: placeholders.Type3OrPlaceholder, args: Iterable[placeholders.Type3OrPlaceholder], comment: str):
super().__init__(comment=comment) super().__init__(comment=comment)
self.exp_type = exp_type self.exp_type = exp_type
@ -348,8 +349,12 @@ class LiteralFitsConstraint(ConstraintBase):
LiteralFitsConstraint(x, y) LiteralFitsConstraint(x, y)
for x, y in zip(tp_args, self.literal.value, strict=True) for x, y in zip(tp_args, self.literal.value, strict=True)
) )
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend( res.extend(
SameTypeConstraint(x, y.type3) SameTypeConstraint(x, PlaceholderForType([y]))
for x, y in zip(tp_args, self.literal.value, strict=True) for x, y in zip(tp_args, self.literal.value, strict=True)
) )
@ -371,8 +376,12 @@ class LiteralFitsConstraint(ConstraintBase):
LiteralFitsConstraint(sa_type, y) LiteralFitsConstraint(sa_type, y)
for y in self.literal.value for y in self.literal.value
) )
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend( res.extend(
SameTypeConstraint(sa_type, y.type3) SameTypeConstraint(sa_type, PlaceholderForType([y]))
for y in self.literal.value for y in self.literal.value
) )
@ -396,8 +405,12 @@ class LiteralFitsConstraint(ConstraintBase):
LiteralFitsConstraint(x, y) LiteralFitsConstraint(x, y)
for x, y in zip(st_args.values(), self.literal.value, strict=True) for x, y in zip(st_args.values(), self.literal.value, strict=True)
) )
# Generate placeholders so each Literal expression
# gets updated when we figure out the type of the
# expression the literal is used in
res.extend( res.extend(
SameTypeConstraint(x_t, y.type3, comment=f'{self.literal.struct_name}.{x_n}') SameTypeConstraint(x_t, PlaceholderForType([y]), comment=f'{self.literal.struct_name}.{x_n}')
for (x_n, x_t, ), y in zip(st_args.items(), self.literal.value, strict=True) for (x_n, x_t, ), y in zip(st_args.items(), self.literal.value, strict=True)
) )
@ -421,20 +434,27 @@ class CanBeSubscriptedConstraint(ConstraintBase):
""" """
A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array) A value that is subscipted, i.e. a[0] (tuple) or a[b] (static array)
""" """
__slots__ = ('ret_type3', 'type3', 'index', 'index_type3', ) __slots__ = ('ret_type3', 'type3', 'index', 'index_phft', )
ret_type3: placeholders.Type3OrPlaceholder ret_type3: placeholders.Type3OrPlaceholder
type3: placeholders.Type3OrPlaceholder type3: placeholders.Type3OrPlaceholder
index: ourlang.Expression index: ourlang.Expression
index_type3: placeholders.Type3OrPlaceholder index_phft: placeholders.Type3OrPlaceholder
def __init__(self, ret_type3: placeholders.Type3OrPlaceholder, type3: placeholders.Type3OrPlaceholder, index: ourlang.Expression, comment: Optional[str] = None) -> None: def __init__(
self,
ret_type3: placeholders.PlaceholderForType,
type3: placeholders.PlaceholderForType,
index: ourlang.Expression,
index_phft: placeholders.PlaceholderForType,
comment: Optional[str] = None,
) -> None:
super().__init__(comment=comment) super().__init__(comment=comment)
self.ret_type3 = ret_type3 self.ret_type3 = ret_type3
self.type3 = type3 self.type3 = type3
self.index = index self.index = index
self.index_type3 = index.type3 self.index_phft = index_phft
def check(self) -> CheckResult: def check(self) -> CheckResult:
exp_type = self.type3 exp_type = self.type3
@ -451,7 +471,7 @@ class CanBeSubscriptedConstraint(ConstraintBase):
sa_type, sa_len = sa_args sa_type, sa_len = sa_args
result: List[ConstraintBase] = [ result: List[ConstraintBase] = [
SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), SameTypeConstraint(prelude.u32, self.index_phft, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'), SameTypeConstraint(sa_type, self.ret_type3, comment='([]) :: Subscriptable a => a b -> u32 -> b'),
] ]
@ -478,13 +498,13 @@ class CanBeSubscriptedConstraint(ConstraintBase):
return Error('Tuple index out of range') return Error('Tuple index out of range')
return [ return [
SameTypeConstraint(prelude.u32, self.index_type3, comment=f'Tuple subscript index {self.index.value}'), SameTypeConstraint(prelude.u32, self.index_phft, comment=f'Tuple subscript index {self.index.value}'),
SameTypeConstraint(tp_args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'), SameTypeConstraint(tp_args[self.index.value], self.ret_type3, comment=f'Tuple subscript index {self.index.value}'),
] ]
if exp_type is prelude.bytes_: if exp_type is prelude.bytes_:
return [ return [
SameTypeConstraint(prelude.u32, self.index_type3, comment='([]) :: bytes -> u32 -> u8'), SameTypeConstraint(prelude.u32, self.index_phft, comment='([]) :: bytes -> u32 -> u8'),
SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'), SameTypeConstraint(prelude.u8, self.ret_type3, comment='([]) :: bytes -> u32 -> u8'),
] ]

View File

@ -19,6 +19,7 @@ from .constraints import (
SameTypeConstraint, SameTypeConstraint,
TupleMatchConstraint, TupleMatchConstraint,
) )
from .placeholders import PlaceholderForType
ConstraintGenerator = Generator[ConstraintBase, None, None] ConstraintGenerator = Generator[ConstraintBase, None, None]
@ -28,40 +29,50 @@ def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase
return [*module(ctx, inp)] return [*module(ctx, inp)]
def constant(ctx: Context, inp: ourlang.Constant) -> ConstraintGenerator: def constant(ctx: Context, inp: ourlang.Constant, phft: placeholders.PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)): if isinstance(inp, (ourlang.ConstantPrimitive, ourlang.ConstantBytes, ourlang.ConstantTuple, ourlang.ConstantStruct)):
yield LiteralFitsConstraint( yield LiteralFitsConstraint(
inp.type3, inp, phft, inp,
comment='The given literal must fit the expected type' comment='The given literal must fit the expected type'
) )
return return
raise NotImplementedError(constant, inp) raise NotImplementedError(constant, inp)
def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: def expression(ctx: Context, inp: ourlang.Expression, phft: placeholders.PlaceholderForType) -> ConstraintGenerator:
if isinstance(inp, ourlang.Constant): if isinstance(inp, ourlang.Constant):
yield from constant(ctx, inp) yield from constant(ctx, inp, phft)
return return
if isinstance(inp, ourlang.VariableReference): if isinstance(inp, ourlang.VariableReference):
yield SameTypeConstraint(inp.variable.type3, inp.type3, yield SameTypeConstraint(inp.variable.type3, phft,
comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})') comment=f'typeOf("{inp.variable.name}") == typeOf({inp.variable.name})')
return return
if isinstance(inp, ourlang.BinaryOp) or isinstance(inp, ourlang.FunctionCall): if isinstance(inp, ourlang.BinaryOp) or isinstance(inp, ourlang.FunctionCall):
signature = inp.operator.signature if isinstance(inp, ourlang.BinaryOp) else inp.function.signature
arguments = [inp.left, inp.right] if isinstance(inp, ourlang.BinaryOp) else inp.arguments
func_name = f'({inp.operator.name})' if isinstance(inp, ourlang.BinaryOp) else inp.function.name func_name = f'({inp.operator.name})' if isinstance(inp, ourlang.BinaryOp) else inp.function.name
arguments = [inp.left, inp.right] if isinstance(inp, ourlang.BinaryOp) else inp.arguments
arg_placeholders = {
arg_expr: PlaceholderForType([arg_expr])
for arg_expr in arguments
}
arg_placeholders[inp] = phft
for call_arg in arguments:
yield from expression(ctx, call_arg, arg_placeholders[call_arg])
signature = inp.operator.signature if isinstance(inp, ourlang.BinaryOp) else inp.function.signature
type_var_map = { type_var_map = {
x: placeholders.PlaceholderForType([]) x: placeholders.PlaceholderForType([])
for x in signature.args for x in signature.args
if isinstance(x, functions.TypeVariable) if isinstance(x, functions.TypeVariable)
or isinstance(x, functions.TypeConstructorVariable)
} }
for call_arg in arguments: for arg_expr in arguments:
yield from expression(ctx, call_arg) yield from expression(ctx, arg_expr, arg_placeholders[arg_expr])
for constraint in signature.context.constraints: for constraint in signature.context.constraints:
if isinstance(constraint, functions.Constraint_TypeClassInstanceExists): if isinstance(constraint, functions.Constraint_TypeClassInstanceExists):
@ -81,11 +92,11 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument' comment = f'The type of the value passed to argument {arg_no} of function {func_name} should match the type of that argument'
if isinstance(sig_part, functions.TypeVariable): if isinstance(sig_part, functions.TypeVariable):
yield SameTypeConstraint(type_var_map[sig_part], arg_expr.type3, comment=comment) yield SameTypeConstraint(type_var_map[sig_part], arg_placeholders[arg_expr], comment=comment)
continue continue
if isinstance(sig_part, type3types.Type3): if isinstance(sig_part, type3types.Type3):
yield SameTypeConstraint(sig_part, arg_expr.type3, comment=comment) yield SameTypeConstraint(sig_part, arg_placeholders[arg_expr], comment=comment)
continue continue
raise NotImplementedError(sig_part) raise NotImplementedError(sig_part)
@ -94,11 +105,12 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
if isinstance(inp, ourlang.TupleInstantiation): if isinstance(inp, ourlang.TupleInstantiation):
r_type = [] r_type = []
for arg in inp.elements: for arg in inp.elements:
yield from expression(ctx, arg) arg_phft = PlaceholderForType([arg])
r_type.append(arg.type3) yield from expression(ctx, arg, arg_phft)
r_type.append(arg_phft)
yield TupleMatchConstraint( yield TupleMatchConstraint(
inp.type3, phft,
r_type, r_type,
comment='The type of a tuple is a combination of its members' comment='The type of a tuple is a combination of its members'
) )
@ -106,10 +118,13 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
return return
if isinstance(inp, ourlang.Subscript): if isinstance(inp, ourlang.Subscript):
yield from expression(ctx, inp.varref) varref_phft = PlaceholderForType([inp.varref])
yield from expression(ctx, inp.index) index_phft = PlaceholderForType([inp.index])
yield CanBeSubscriptedConstraint(inp.type3, inp.varref.type3, inp.index) yield from expression(ctx, inp.varref, varref_phft)
yield from expression(ctx, inp.index, index_phft)
yield CanBeSubscriptedConstraint(phft, varref_phft, inp.index, index_phft)
return return
if isinstance(inp, ourlang.AccessStructMember): if isinstance(inp, ourlang.AccessStructMember):
@ -117,33 +132,40 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator:
st_args = prelude.struct.did_construct(inp.struct_type3) st_args = prelude.struct.did_construct(inp.struct_type3)
assert st_args is not None # FIXME: See test_struct.py::test_struct_not_accessible assert st_args is not None # FIXME: See test_struct.py::test_struct_not_accessible
yield from expression(ctx, inp.varref) yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO
yield SameTypeConstraint(st_args[inp.member], inp.type3, yield SameTypeConstraint(st_args[inp.member], phft,
comment=f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}') comment=f'The type of a struct member reference is the same as the type of struct member {inp.struct_type3.name}.{inp.member}')
return return
if isinstance(inp, ourlang.Fold): if isinstance(inp, ourlang.Fold):
yield from expression(ctx, inp.base) base_phft = PlaceholderForType([inp.base])
yield from expression(ctx, inp.iter) iter_phft = PlaceholderForType([inp.iter])
yield SameTypeConstraint(inp.func.posonlyargs[0].type3, inp.func.returns_type3, inp.base.type3, inp.type3, yield from expression(ctx, inp.base, base_phft)
yield from expression(ctx, inp.iter, iter_phft)
yield SameTypeConstraint(inp.func.posonlyargs[0].type3, inp.func.returns_type3, base_phft, phft,
comment='foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b') comment='foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b')
yield MustImplementTypeClassConstraint(ctx, 'Foldable', [inp.iter.type3]) yield MustImplementTypeClassConstraint(ctx, 'Foldable', [iter_phft])
return return
raise NotImplementedError(expression, inp) raise NotImplementedError(expression, inp)
def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator: def statement_return(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementReturn) -> ConstraintGenerator:
yield from expression(ctx, inp.value) phft = PlaceholderForType([inp.value])
yield SameTypeConstraint(fun.returns_type3, inp.value.type3, yield from expression(ctx, inp.value, phft)
yield SameTypeConstraint(fun.returns_type3, phft,
comment=f'The type of the value returned from function {fun.name} should match its return type') 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: def statement_if(ctx: Context, fun: ourlang.Function, inp: ourlang.StatementIf) -> ConstraintGenerator:
yield from expression(ctx, inp.test) test_phft = PlaceholderForType([inp.test])
yield SameTypeConstraint(inp.test.type3, prelude.bool_, yield from expression(ctx, inp.test, test_phft)
yield SameTypeConstraint(test_phft, prelude.bool_,
comment='Must pass a boolean expression to if') comment='Must pass a boolean expression to if')
for stmt in inp.statements: for stmt in inp.statements:
@ -173,8 +195,10 @@ def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator:
yield from statement(ctx, inp, stmt) yield from statement(ctx, inp, stmt)
def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator: def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator:
yield from constant(ctx, inp.constant) phft = PlaceholderForType([inp.constant])
yield SameTypeConstraint(inp.type3, inp.constant.type3,
yield from constant(ctx, inp.constant, phft)
yield SameTypeConstraint(inp.type3, phft,
comment=f'The type of the value for module constant definition {inp.name} should match the type of that constant') comment=f'The type of the value for module constant definition {inp.name} should match the type of that constant')
def module(ctx: Context, inp: ourlang.Module) -> ConstraintGenerator: def module(ctx: Context, inp: ourlang.Module) -> ConstraintGenerator:

View File

@ -115,8 +115,6 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
# FIXME: This doesn't work with e.g. `:: [a] -> a`, as the placeholder is inside a type # FIXME: This doesn't work with e.g. `:: [a] -> a`, as the placeholder is inside a type
for plh, typ in placeholder_substitutes.items(): for plh, typ in placeholder_substitutes.items():
for expr in plh.update_on_substitution: for expr in plh.update_on_substitution:
assert expr.type3 is plh
expr.type3 = typ expr.type3 = typ
def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None: def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None:

View File

@ -34,6 +34,25 @@ class TypeVariable:
def __repr__(self) -> str: def __repr__(self) -> str:
return f'TypeVariable({repr(self.letter)})' return f'TypeVariable({repr(self.letter)})'
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.
"""
__slots__ = ('letter', 'args', )
def __init__(self, letter: str, args: Iterable[TypeVariable]) -> None:
self.letter = letter
self.args = list(args)
def __call__(self, tvar: TypeVariable) -> 'TypeConstructorVariable':
return TypeConstructorVariable(self.letter, self.args + [tvar])
def __repr__(self) -> str:
return f'TypeConstructorVariable({self.letter!r}, {self.args!r})'
class ConstraintBase: class ConstraintBase:
__slots__ = () __slots__ = ()
@ -41,9 +60,9 @@ class Constraint_TypeClassInstanceExists(ConstraintBase):
__slots__ = ('type_class3', 'types', ) __slots__ = ('type_class3', 'types', )
type_class3: 'Type3Class' type_class3: 'Type3Class'
types: list[TypeVariable] types: list[Union[TypeVariable, TypeConstructorVariable]]
def __init__(self, type_class3: 'Type3Class', types: Iterable[TypeVariable]) -> None: def __init__(self, type_class3: 'Type3Class', types: Iterable[Union[TypeVariable, TypeConstructorVariable]]) -> None:
self.type_class3 = type_class3 self.type_class3 = type_class3
self.types = list(types) self.types = list(types)
@ -52,27 +71,22 @@ class Constraint_TypeClassInstanceExists(ConstraintBase):
assert len(self.type_class3.args) == len(self.types) assert len(self.type_class3.args) == len(self.types)
class TypeVariableContext: class TypeVariableContext:
__slots__ = ('variables', 'constraints', ) __slots__ = ('constraints', )
variables: set[TypeVariable]
constraints: list[ConstraintBase] constraints: list[ConstraintBase]
def __init__(self) -> None: def __init__(self, constraints: Iterable[ConstraintBase] = ()) -> None:
self.variables = set() self.constraints = list(constraints)
self.constraints = []
def __copy__(self) -> 'TypeVariableContext': def __copy__(self) -> 'TypeVariableContext':
result = TypeVariableContext() return TypeVariableContext(self.constraints)
result.variables.update(self.variables)
result.constraints.extend(self.constraints)
return result
class FunctionSignature: class FunctionSignature:
__slots__ = ('context', 'args', ) __slots__ = ('context', 'args', )
context: TypeVariableContext context: TypeVariableContext
args: List[Union['Type3', TypeVariable]] args: List[Union['Type3', TypeVariable, TypeConstructorVariable]]
def __init__(self, context: TypeVariableContext, args: Iterable[Union['Type3', TypeVariable]]) -> None: def __init__(self, context: TypeVariableContext, args: Iterable[Union['Type3', TypeVariable, TypeConstructorVariable]]) -> None:
self.context = context.__copy__() self.context = context.__copy__()
self.args = list(args) self.args = list(args)

View File

@ -7,14 +7,13 @@ from typing import Any, Iterable, List, Optional, Protocol, Union
from .types import Type3 from .types import Type3
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
class ExpressionProtocol(Protocol): class ExpressionProtocol(Protocol):
""" """
A protocol for classes that should be updated on substitution A protocol for classes that should be updated on substitution
""" """
type3: 'Type3OrPlaceholder' type3: Type3 | None
""" """
The type to update The type to update
""" """

View File

@ -2,7 +2,9 @@ from typing import Dict, Iterable, List, Mapping, Optional, Union
from .functions import ( from .functions import (
Constraint_TypeClassInstanceExists, Constraint_TypeClassInstanceExists,
ConstraintBase,
FunctionSignature, FunctionSignature,
TypeConstructorVariable,
TypeVariable, TypeVariable,
TypeVariableContext, TypeVariableContext,
) )
@ -26,7 +28,7 @@ class Type3Class:
__slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', ) __slots__ = ('name', 'args', 'methods', 'operators', 'inherited_classes', )
name: str name: str
args: List[TypeVariable] args: List[Union[TypeVariable, TypeConstructorVariable]]
methods: Dict[str, Type3ClassMethod] methods: Dict[str, Type3ClassMethod]
operators: Dict[str, Type3ClassMethod] operators: Dict[str, Type3ClassMethod]
inherited_classes: List['Type3Class'] inherited_classes: List['Type3Class']
@ -34,10 +36,11 @@ class Type3Class:
def __init__( def __init__(
self, self,
name: str, name: str,
args: Iterable[TypeVariable], args: Iterable[Union[TypeVariable, TypeConstructorVariable]],
methods: Mapping[str, Iterable[Union[Type3, TypeVariable]]], methods: Mapping[str, Iterable[Union[Type3, TypeVariable, TypeConstructorVariable]]],
operators: Mapping[str, Iterable[Union[Type3, TypeVariable]]], operators: Mapping[str, Iterable[Union[Type3, TypeVariable, TypeConstructorVariable]]],
inherited_classes: Optional[List['Type3Class']] = None, inherited_classes: Optional[List['Type3Class']] = None,
additional_context: Optional[Mapping[str, Iterable[ConstraintBase]]] = None,
) -> None: ) -> None:
self.name = name self.name = name
self.args = list(args) self.args = list(args)
@ -55,5 +58,12 @@ class Type3Class:
} }
self.inherited_classes = inherited_classes or [] self.inherited_classes = inherited_classes or []
if additional_context:
for func_name, constraint_list in additional_context.items():
func = self.methods.get(func_name) or self.operators.get(func_name)
assert func is not None # type hint
func.signature.context.constraints.extend(constraint_list)
def __repr__(self) -> str: def __repr__(self) -> str:
return self.name return self.name

View File

@ -106,7 +106,7 @@ class TypeConstructor(Generic[T]):
The name of the type constructor The name of the type constructor
""" """
on_create: Callable[[Type3], None] on_create: Callable[[T, Type3], None]
""" """
Who to let know if a type is created Who to let know if a type is created
""" """
@ -122,7 +122,7 @@ class TypeConstructor(Generic[T]):
Sometimes we need to know the key that created a type. Sometimes we need to know the key that created a type.
""" """
def __init__(self, name: str, on_create: Callable[[Type3], None]) -> None: def __init__(self, name: str, on_create: Callable[[T, Type3], None]) -> None:
self.name = name self.name = name
self.on_create = on_create self.on_create = on_create
@ -152,7 +152,7 @@ class TypeConstructor(Generic[T]):
if result is None: if result is None:
self._cache[key] = result = Type3(self.make_name(key)) self._cache[key] = result = Type3(self.make_name(key))
self._reverse_cache[result] = key self._reverse_cache[result] = key
self.on_create(result) self.on_create(key, result)
return result return result

View File

@ -5,7 +5,6 @@ from typing import Any, Generator, Iterable, List, TextIO, Union
from phasm import compiler, prelude from phasm import compiler, prelude
from phasm.codestyle import phasm_render from phasm.codestyle import phasm_render
from phasm.runtime import calculate_alloc_size from phasm.runtime import calculate_alloc_size
from phasm.type3 import placeholders as type3placeholders
from phasm.type3 import types as type3types from phasm.type3 import types as type3types
from . import runners from . import runners
@ -65,9 +64,6 @@ class Suite:
runner.interpreter_dump_memory(sys.stderr) runner.interpreter_dump_memory(sys.stderr)
for arg, arg_typ in zip(args, func_args, strict=True): for arg, arg_typ in zip(args, func_args, strict=True):
assert not isinstance(arg_typ, type3placeholders.PlaceholderForType), \
'Cannot call polymorphic function from outside'
if arg_typ in (prelude.u8, prelude.u32, prelude.u64, ): if arg_typ in (prelude.u8, prelude.u32, prelude.u64, ):
assert isinstance(arg, int) assert isinstance(arg, int)
wasm_args.append(arg) wasm_args.append(arg)
@ -88,7 +84,6 @@ class Suite:
wasm_args.append(adr) wasm_args.append(adr)
continue continue
assert isinstance(arg_typ, type3types.Type3)
sa_args = prelude.static_array.did_construct(arg_typ) sa_args = prelude.static_array.did_construct(arg_typ)
if sa_args is not None: if sa_args is not None:
adr = _allocate_memory_stored_value(runner, arg_typ, arg) adr = _allocate_memory_stored_value(runner, arg_typ, arg)
@ -222,8 +217,6 @@ def _allocate_memory_stored_value(
offset = adr offset = adr
for val_el_val, val_el_typ in zip(val, tp_args, strict=True): for val_el_val, val_el_typ in zip(val, tp_args, strict=True):
assert not isinstance(val_el_typ, type3placeholders.PlaceholderForType)
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr return adr
@ -240,8 +233,6 @@ def _allocate_memory_stored_value(
offset = adr offset = adr
for val_el_name, val_el_typ in st_args.items(): for val_el_name, val_el_typ in st_args.items():
assert not isinstance(val_el_typ, type3placeholders.PlaceholderForType)
val_el_val = val[val_el_name] val_el_val = val[val_el_name]
offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val) offset += _write_memory_stored_value(runner, offset, val_el_typ, val_el_val)
return adr return adr
@ -299,8 +290,6 @@ def _load_memory_stored_returned_value(
return _load_bytes_from_address(runner, ret_type3, wasm_value) return _load_bytes_from_address(runner, ret_type3, wasm_value)
assert isinstance(ret_type3, type3types.Type3) # Type hint
sa_args = prelude.static_array.did_construct(ret_type3) sa_args = prelude.static_array.did_construct(ret_type3)
if sa_args is not None: if sa_args is not None:
assert isinstance(wasm_value, int), wasm_value assert isinstance(wasm_value, int), wasm_value
@ -366,8 +355,6 @@ def _unpack(runner: runners.RunnerBase, typ: type3types.Type3, inp: bytes) -> An
assert len(inp) == 4 assert len(inp) == 4
adr = struct.unpack('<I', inp)[0] adr = struct.unpack('<I', inp)[0]
assert isinstance(typ, type3types.Type3)
sa_args = prelude.static_array.did_construct(typ) sa_args = prelude.static_array.did_construct(typ)
if sa_args is not None: if sa_args is not None:
sa_type, sa_len = sa_args sa_type, sa_len = sa_args
@ -404,9 +391,6 @@ def _split_read_bytes(all_bytes: bytes, split_sizes: Iterable[int]) -> Generator
def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3types.Type3, len_typ: type3types.IntType3, adr: int) -> Any: def _load_static_array_from_address(runner: runners.RunnerBase, sub_typ: type3types.Type3, len_typ: type3types.IntType3, adr: int) -> Any:
sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n') sys.stderr.write(f'Reading 0x{adr:08x} {sub_typ:s} {len_typ:s}\n')
assert not isinstance(sub_typ, type3placeholders.PlaceholderForType)
assert isinstance(len_typ, type3types.IntType3)
sa_len = len_typ.value sa_len = len_typ.value
arg_size_1 = calculate_alloc_size(sub_typ, is_member=True) arg_size_1 = calculate_alloc_size(sub_typ, is_member=True)

View File

@ -0,0 +1,55 @@
import pytest
from phasm.type3.entry import Type3Exception
from ..helpers import Suite
@pytest.mark.integration_test
def test_foldable_sum():
code_py = """
@exported
def testEntry(x: i32[5]) -> i32:
return sum(x)
"""
result = Suite(code_py).run_code((4, 5, 6, 7, 8, ))
assert 30 == result.returned_value
@pytest.mark.integration_test
def test_foldable_sum_not_natnum():
code_py = """
class Foo:
bar: i32
@exported
def testEntry(x: Foo[4]) -> Foo:
return sum(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: NatNum Foo'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_foldable_invalid_return_type():
code_py = """
@exported
def testEntry(x: i32[5]) -> f64:
return sum(x)
"""
with pytest.raises(Type3Exception, match='f64 must be i32 instead'):
Suite(code_py).run_code((4, 5, 6, 7, 8, ))
@pytest.mark.integration_test
def test_foldable_not_foldable():
code_py = """
@exported
def testEntry(x: i32) -> i32:
return sum(x)
"""
with pytest.raises(Type3Exception, match='Missing type class instantation: Foldable i32'):
Suite(code_py).run_code()

View File

@ -6,7 +6,20 @@ from ..helpers import Suite
@pytest.mark.integration_test @pytest.mark.integration_test
def test_expr_constant_literal_does_not_fit(): def test_expr_constant_literal_does_not_fit_module_constant():
code_py = """
CONSTANT: u8 = 1000
@exported
def testEntry() -> u8:
return CONSTANT
"""
with pytest.raises(Type3Exception, match=r'Must fit in 1 byte\(s\)'):
Suite(code_py).run_code()
@pytest.mark.integration_test
def test_expr_constant_literal_does_not_fit_return():
code_py = """ code_py = """
@exported @exported
def testEntry() -> u8: def testEntry() -> u8: