From d9a08cf0f775113ec551e6d561a00ee2f0068de6 Mon Sep 17 00:00:00 2001 From: "Johan B.W. de Vries" Date: Mon, 5 May 2025 17:33:21 +0200 Subject: [PATCH] Chore: Placeholders are now internal They were exposed on AST, causing confusion. Now they're only used in constraints. --- phasm/ourlang.py | 11 ++- phasm/type3/constraints.py | 44 ++++++++---- phasm/type3/constraintsgenerator.py | 74 +++++++++++++------- phasm/type3/entry.py | 2 - phasm/type3/placeholders.py | 2 +- tests/integration/test_lang/test_literals.py | 15 +++- 6 files changed, 99 insertions(+), 49 deletions(-) diff --git a/phasm/ourlang.py b/phasm/ourlang.py index b462a41..a4b1618 100644 --- a/phasm/ourlang.py +++ b/phasm/ourlang.py @@ -6,7 +6,6 @@ from typing import Dict, Iterable, List, Optional, Union from . import prelude from .type3.functions import FunctionSignature, TypeVariableContext -from .type3.placeholders import PlaceholderForType, Type3OrPlaceholder from .type3.typeclasses import Type3ClassMethod from .type3.types import Type3 @@ -17,10 +16,10 @@ class Expression: """ __slots__ = ('type3', ) - type3: Type3OrPlaceholder + type3: Type3 | None def __init__(self) -> None: - self.type3 = PlaceholderForType([self]) + self.type3 = None class Constant(Expression): """ @@ -198,10 +197,10 @@ class AccessStructMember(Expression): __slots__ = ('varref', 'struct_type3', 'member', ) varref: VariableReference - struct_type3: Type3OrPlaceholder + struct_type3: Type3 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__() self.varref = varref @@ -284,7 +283,7 @@ class FunctionParam: __slots__ = ('name', 'type3', ) name: str - type3: Type3OrPlaceholder + type3: Type3 def __init__(self, name: str, type3: Type3) -> None: self.name = name diff --git a/phasm/type3/constraints.py b/phasm/type3/constraints.py index 111fdd3..4b5a24c 100644 --- a/phasm/type3/constraints.py +++ b/phasm/type3/constraints.py @@ -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. """ -from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, Iterable, List, Optional, Tuple, Union from .. import ourlang, prelude from . import placeholders, typeclasses, types +from .placeholders import PlaceholderForType class Error: @@ -157,7 +158,7 @@ class SameTypeConstraint(ConstraintBase): return f'SameTypeConstraint({args}, comment={repr(self.comment)})' 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) self.exp_type = exp_type @@ -348,8 +349,12 @@ class LiteralFitsConstraint(ConstraintBase): LiteralFitsConstraint(x, y) for x, y in zip(tp_args, self.literal.value, strict=True) ) + + # Generate placeholders so each Literal expression + # gets updated when we figure out the type of the + # expression the literal is used in res.extend( - SameTypeConstraint(x, y.type3) + SameTypeConstraint(x, PlaceholderForType([y])) for x, y in zip(tp_args, self.literal.value, strict=True) ) @@ -371,8 +376,12 @@ class LiteralFitsConstraint(ConstraintBase): LiteralFitsConstraint(sa_type, y) for y in self.literal.value ) + + # Generate placeholders so each Literal expression + # gets updated when we figure out the type of the + # expression the literal is used in res.extend( - SameTypeConstraint(sa_type, y.type3) + SameTypeConstraint(sa_type, PlaceholderForType([y])) for y in self.literal.value ) @@ -396,8 +405,12 @@ class LiteralFitsConstraint(ConstraintBase): LiteralFitsConstraint(x, y) 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( - 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) ) @@ -421,20 +434,27 @@ class CanBeSubscriptedConstraint(ConstraintBase): """ 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 type3: placeholders.Type3OrPlaceholder 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) self.ret_type3 = ret_type3 self.type3 = type3 self.index = index - self.index_type3 = index.type3 + self.index_phft = index_phft def check(self) -> CheckResult: exp_type = self.type3 @@ -451,7 +471,7 @@ class CanBeSubscriptedConstraint(ConstraintBase): sa_type, sa_len = sa_args 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'), ] @@ -478,13 +498,13 @@ class CanBeSubscriptedConstraint(ConstraintBase): return Error('Tuple index out of range') 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}'), ] if exp_type is prelude.bytes_: 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'), ] diff --git a/phasm/type3/constraintsgenerator.py b/phasm/type3/constraintsgenerator.py index ba95a86..8a9c37b 100644 --- a/phasm/type3/constraintsgenerator.py +++ b/phasm/type3/constraintsgenerator.py @@ -19,6 +19,7 @@ from .constraints import ( SameTypeConstraint, TupleMatchConstraint, ) +from .placeholders import PlaceholderForType ConstraintGenerator = Generator[ConstraintBase, None, None] @@ -28,23 +29,23 @@ def phasm_type3_generate_constraints(inp: ourlang.Module) -> List[ConstraintBase 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)): yield LiteralFitsConstraint( - inp.type3, inp, + phft, inp, comment='The given literal must fit the expected type' ) return 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): - yield from constant(ctx, inp) + yield from constant(ctx, inp, phft) return 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})') return @@ -60,8 +61,14 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: if isinstance(x, functions.TypeVariable) } - for call_arg in arguments: - yield from expression(ctx, call_arg) + arg_placeholders = { + arg_expr: PlaceholderForType([arg_expr]) + for arg_expr in arguments + } + arg_placeholders[inp] = phft + + for arg_expr in arguments: + yield from expression(ctx, arg_expr, arg_placeholders[arg_expr]) for constraint in signature.context.constraints: if isinstance(constraint, functions.Constraint_TypeClassInstanceExists): @@ -81,11 +88,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' 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 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 raise NotImplementedError(sig_part) @@ -94,11 +101,12 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: if isinstance(inp, ourlang.TupleInstantiation): r_type = [] for arg in inp.elements: - yield from expression(ctx, arg) - r_type.append(arg.type3) + arg_phft = PlaceholderForType([arg]) + yield from expression(ctx, arg, arg_phft) + r_type.append(arg_phft) yield TupleMatchConstraint( - inp.type3, + phft, r_type, comment='The type of a tuple is a combination of its members' ) @@ -106,10 +114,13 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: return if isinstance(inp, ourlang.Subscript): - yield from expression(ctx, inp.varref) - yield from expression(ctx, inp.index) + varref_phft = PlaceholderForType([inp.varref]) + 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 if isinstance(inp, ourlang.AccessStructMember): @@ -117,33 +128,40 @@ def expression(ctx: Context, inp: ourlang.Expression) -> ConstraintGenerator: st_args = prelude.struct.did_construct(inp.struct_type3) assert st_args is not None # FIXME: See test_struct.py::test_struct_not_accessible - yield from expression(ctx, inp.varref) - yield SameTypeConstraint(st_args[inp.member], inp.type3, + yield from expression(ctx, inp.varref, PlaceholderForType([inp.varref])) # TODO + 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}') return if isinstance(inp, ourlang.Fold): - yield from expression(ctx, inp.base) - yield from expression(ctx, inp.iter) + base_phft = PlaceholderForType([inp.base]) + 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') - yield MustImplementTypeClassConstraint(ctx, 'Foldable', [inp.iter.type3]) + yield MustImplementTypeClassConstraint(ctx, 'Foldable', [iter_phft]) return raise NotImplementedError(expression, inp) 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') 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') for stmt in inp.statements: @@ -173,8 +191,10 @@ def function(ctx: Context, inp: ourlang.Function) -> ConstraintGenerator: yield from statement(ctx, inp, stmt) def module_constant_def(ctx: Context, inp: ourlang.ModuleConstantDef) -> ConstraintGenerator: - yield from constant(ctx, inp.constant) - yield SameTypeConstraint(inp.type3, inp.constant.type3, + phft = PlaceholderForType([inp.constant]) + + 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') def module(ctx: Context, inp: ourlang.Module) -> ConstraintGenerator: diff --git a/phasm/type3/entry.py b/phasm/type3/entry.py index 2bf05c7..818ff53 100644 --- a/phasm/type3/entry.py +++ b/phasm/type3/entry.py @@ -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 for plh, typ in placeholder_substitutes.items(): for expr in plh.update_on_substitution: - assert expr.type3 is plh - expr.type3 = typ def print_constraint(placeholder_id_map: Dict[int, str], constraint: ConstraintBase) -> None: diff --git a/phasm/type3/placeholders.py b/phasm/type3/placeholders.py index 6821eac..a6b6c99 100644 --- a/phasm/type3/placeholders.py +++ b/phasm/type3/placeholders.py @@ -14,7 +14,7 @@ class ExpressionProtocol(Protocol): A protocol for classes that should be updated on substitution """ - type3: 'Type3OrPlaceholder' + type3: Type3 | None """ The type to update """ diff --git a/tests/integration/test_lang/test_literals.py b/tests/integration/test_lang/test_literals.py index 42484cd..5593341 100644 --- a/tests/integration/test_lang/test_literals.py +++ b/tests/integration/test_lang/test_literals.py @@ -6,7 +6,20 @@ from ..helpers import Suite @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 = """ @exported def testEntry() -> u8: