diff --git a/examples/demo.ipynb b/examples/demo.ipynb index 96d84404..2b69f65f 100644 --- a/examples/demo.ipynb +++ b/examples/demo.ipynb @@ -160,13 +160,14 @@ "name": "stderr", "output_type": "stream", "text": [ - "Guppy compilation failed. Error in file :6\n", + "Error: Operator not defined (at :6:11)\n", + " | \n", + "4 | def bad(x: int) -> int:\n", + "5 | # Try to add a tuple to an int\n", + "6 | return x + (x, x)\n", + " | ^^^^^^^^^^ Binary operator `+` not defined for `int` and `(int, int)`\n", "\n", - "4: def bad(x: int) -> int:\n", - "5: # Try to add a tuple to an int\n", - "6: return x + (x, x)\n", - " ^^^^^^^^^^\n", - "GuppyTypeError: Binary operator `+` not defined for arguments of type `int` and `(int, int)`\n" + "Guppy compilation failed due to 1 previous error\n" ] } ], diff --git a/guppylang/checker/expr_checker.py b/guppylang/checker/expr_checker.py index 6bdfe386..81f1a20e 100644 --- a/guppylang/checker/expr_checker.py +++ b/guppylang/checker/expr_checker.py @@ -51,7 +51,7 @@ from guppylang.definition.module import ModuleDef from guppylang.definition.ty import TypeDef from guppylang.definition.value import CallableDef, ValueDef -from guppylang.diagnostic import Error +from guppylang.diagnostic import Error, Help, Note from guppylang.error import ( GuppyError, GuppyTypeError, @@ -77,6 +77,7 @@ TensorCall, TypeApply, ) +from guppylang.span import Span, to_span from guppylang.tys.arg import TypeArg from guppylang.tys.builtin import ( bool_type, @@ -85,6 +86,7 @@ is_list_type, list_type, ) +from guppylang.tys.const import Const from guppylang.tys.param import TypeParam from guppylang.tys.subst import Inst, Subst from guppylang.tys.ty import ( @@ -138,14 +140,219 @@ @dataclass(frozen=True) class TypeMismatchError(Error): - """Error diagnostic for expressions with the wrong type.""" + title: ClassVar[str] = "Type mismatch" + span_label: ClassVar[str] = "Expected {kind} of type `{expected}`, got `{actual}`" expected: Type actual: Type kind: str = "expression" - title: ClassVar[str] = "Type mismatch" - span_label: ClassVar[str] = "Expected {kind} of type `{expected}`, got `{actual}`" + @dataclass(frozen=True) + class CantInferParam(Note): + message: ClassVar[str] = ( + "Couldn't infer an instantiation for type variable `?{type_var}` " + "(higher-rank polymorphic types are not supported)" + ) + type_var: str + + @dataclass(frozen=True) + class CantInstantiateFreeVars(Note): + message: ClassVar[str] = ( + "Can't instantiate parameter `{param}` with type `{illegal_inst}` " + "containing free variables" + ) + param: str + illegal_inst: Type | Const + + +@dataclass(frozen=True) +class TypeInferenceError(Error): + title: ClassVar[str] = "Cannot infer type" + span_label: ClassVar[str] = ( + "Cannot infer type variables in expression of type `{unsolved_ty}`" + ) + unsolved_ty: Type + + +@dataclass(frozen=True) +class UnsupportedError(Error): + title: ClassVar[str] = "Unsupported" + things: str + singular: bool = False + + @property + def rendered_span_label(self) -> str: + is_are = "is" if self.singular else "are" + return f"{self.things} {is_are} not supported" + + +@dataclass(frozen=True) +class IllegalConstant(Error): + title: ClassVar[str] = "Unsupported constant" + span_label: ClassVar[str] = "Type `{ty}` is not supported" + python_ty: type + + +@dataclass(frozen=True) +class IllegalPyExpressionError(Error): + title: ClassVar[str] = "Unsupported Python expression" + span_label: ClassVar[str] = "Expression of type `{python_ty}` is not supported" + python_ty: type + + +@dataclass(frozen=True) +class PyExprNotCPythonError(Error): + title: ClassVar[str] = "Not running CPython" + span_label: ClassVar[str] = ( + "Compile-time `py(...)` expressions are only supported in CPython" + ) + + +@dataclass(frozen=True) +class PyExprNotStaticError(Error): + title: ClassVar[str] = "Not compile-time evaluatable" + span_label: ClassVar[str] = ( + "Guppy variable `{guppy_var}` cannot be accessed in a compile-time `py(...)` " + "expression" + ) + guppy_var: str + + +@dataclass(frozen=True) +class PyExprEvalError(Error): + title: ClassVar[str] = "Python error" + span_label: ClassVar[str] = "Error occurred while evaluating this expression" + message: ClassVar[str] = "Traceback printed below:\n\n{err}" + err: str + + +@dataclass(frozen=True) +class PyExprIncoherentListError(Error): + title: ClassVar[str] = "Unsupported list" + span_label: ClassVar[str] = "List contains elements with different types" + + +@dataclass(frozen=True) +class ModuleMemberNotFoundError(Error): + title: ClassVar[str] = "Not found in module" + span_label: ClassVar[str] = "Module `{module_name}` has no member `{member}`" + module_name: str + member: str + + +@dataclass(frozen=True) +class AttributeNotFoundError(Error): + title: ClassVar[str] = "Attribute not found" + span_label: ClassVar[str] = "Attribute `{attribute}` not found on type `{ty}`" + ty: Type + attribute: str + + +@dataclass(frozen=True) +class UnaryOperatorNotDefinedError(Error): + title: ClassVar[str] = "Operator not defined" + span_label: ClassVar[str] = "Unary operator `{op}` not defined for `{ty}`" + ty: Type + op: str + + +@dataclass(frozen=True) +class BinaryOperatorNotDefinedError(Error): + title: ClassVar[str] = "Operator not defined" + span_label: ClassVar[str] = ( + "Binary operator `{op}` not defined for `{left_ty}` and `{right_ty}`" + ) + left_ty: Type + right_ty: Type + op: str + + +@dataclass(frozen=True) +class BadProtocolError(Error): + title: ClassVar[str] = "Not {is_not}" + span_label: ClassVar[str] = "Expression of type `{ty}` is not {is_not}" + ty: Type + is_not: str + + @dataclass(frozen=True) + class MethodMissing(Help): + message: ClassVar[str] = "Implement missing method: `{method}: {signature}`" + method: str + signature: FunctionType + + @dataclass(frozen=True) + class BadSignature(Help): + message: ClassVar[str] = ( + "Fix signature of method `{ty}.{method}`: Expected `{exp_signature}`, got " + "`{act_signature}`" + ) + ty: Type + method: str + exp_signature: FunctionType + act_signature: FunctionType + + +@dataclass(frozen=True) +class LinearForBreakError(Error): + title: ClassVar[str] = "Break in linear loop" + span_label: ClassVar[str] = "Early exit in linear loops is not allowed" + + @dataclass(frozen=True) + class LinearIteratorType(Note): + span_label: ClassVar[str] = "Iterator has linear type `{ty}`" + ty: Type + + +@dataclass(frozen=True) +class WrongNumberOfArgsError(Error): + title: ClassVar[str] = "" # Custom implementation in `rendered_title` + span_label: ClassVar[str] = "Expected {expected} function arguments, got `{actual}`" + expected: int + actual: int + detailed: bool = True + + @property + def rendered_title(self) -> str: + return ( + "Not enough arguments" + if self.expected > self.actual + else "Too many arguments" + ) + + @property + def rendered_span_label(self) -> str: + if not self.detailed: + return f"Expected {self.expected}, got {self.actual}" + diff = self.expected - self.actual + if diff < 0: + msg = "Unexpected arguments" if diff < -1 else "Unexpected argument" + else: + msg = "Missing arguments" if diff > 1 else "Missing argument" + return f"{msg} (expected {self.expected}, got {self.actual})" + + @dataclass(frozen=True) + class SignatureHint(Note): + message: ClassVar[str] = "Function signature is `{sig}`" + sig: FunctionType + + +@dataclass(frozen=True) +class UnexpectedArgumentError(Error): + title: ClassVar[str] = "Unexpected argument" + span_label: ClassVar[str] = "Expected only {num_args} function arguments" + num_args: int + + +@dataclass(frozen=True) +class Tket2NotInstalled(Error): + title: ClassVar[str] = "Tket2 not installed" + span_label: ClassVar[str] = ( + "Experimental pytket compatibility requires `tket2` to be installed" + ) + + @dataclass(frozen=True) + class InstallInstruction(Help): + message: ClassVar[str] = "Install tket2: `pip install tket2`" class ExprChecker(AstVisitor[tuple[ast.expr, Subst]]): @@ -248,9 +455,7 @@ def visit_DesugaredListComp( def visit_Call(self, node: ast.Call, ty: Type) -> tuple[ast.expr, Subst]: if len(node.keywords) > 0: - raise GuppyError( - "Argument passing by keyword is not supported", node.keywords[0] - ) + raise GuppyError(UnsupportedError(node.keywords[0], "Keyword arguments")) node.func, func_ty = self._synthesize(node.func, allow_free_vars=False) # First handle direct calls of user-defined functions and extension functions @@ -278,8 +483,8 @@ def visit_Call(self, node: ast.Call, ty: Type) -> tuple[ast.expr, Subst]: ): check_function_tensors_enabled(node.func) if any(f.parametrized for f in function_elements): - raise GuppyTypeError( - "Polymorphic functions in tuples are not supported", node.func + raise GuppyError( + UnsupportedError(node.func, "Polymorphic function tensors") ) tensor_ty = function_tensor_signature(function_elements) @@ -300,7 +505,7 @@ def visit_Call(self, node: ast.Call, ty: Type) -> tuple[ast.expr, Subst]: def visit_PyExpr(self, node: PyExpr, ty: Type) -> tuple[ast.expr, Subst]: python_val = eval_py_expr(node, self.ctx) - if act := python_value_to_guppy_type(python_val, node, self.ctx.globals): + if act := python_value_to_guppy_type(python_val, node.value, self.ctx.globals): subst = unify(ty, act, {}) if subst is None: self._fail(ty, act, node) @@ -308,10 +513,7 @@ def visit_PyExpr(self, node: PyExpr, ty: Type) -> tuple[ast.expr, Subst]: subst = {x: s for x, s in subst.items() if x in ty.unsolved_vars} return with_type(act, with_loc(node, ast.Constant(value=python_val))), subst - raise GuppyError( - f"Python expression of type `{type(python_val)}` is not supported by Guppy", - node, - ) + raise GuppyError(IllegalPyExpressionError(node.value, type(python_val))) def generic_visit(self, node: ast.expr, ty: Type) -> tuple[ast.expr, Subst]: # Try to synthesize and then check if we can unify it with the given type @@ -342,9 +544,7 @@ def synthesize( return node, ty node, ty = self.visit(node) if ty.unsolved_vars and not allow_free_vars: - raise GuppyTypeError( - f"Cannot infer type variable in expression of type `{ty}`", node - ) + raise GuppyError(TypeInferenceError(node, ty)) return with_type(ty, node), ty def _check( @@ -356,7 +556,7 @@ def _check( def visit_Constant(self, node: ast.Constant) -> tuple[ast.expr, Type]: ty = python_value_to_guppy_type(node.value, node, self.ctx.globals) if ty is None: - raise GuppyError("Unsupported constant", node) + raise GuppyError(IllegalConstant(node, type(node.value))) return node, ty def visit_Name(self, node: ast.Name) -> tuple[ast.expr, Type]: @@ -390,11 +590,15 @@ def _check_global( ) def visit_Attribute(self, node: ast.Attribute) -> tuple[ast.expr, Type]: - # A `value.attr` attribute access + # A `value.attr` attribute access. Unfortunately, the `attr` is just a string, + # not an AST node, so we have to compute its span by hand. This is fine since + # linebreaks are not allowed in the identifier following the `.` + span = to_span(node) + attr_span = Span(span.end.shift_left(len(node.attr)), span.end) if module_def := self._is_module_def(node.value): if node.attr not in module_def.globals: raise GuppyError( - f"Module `{module_def.name}` has no member `{node.attr}`", node + ModuleMemberNotFoundError(attr_span, module_def.name, node.attr) ) defn = module_def.globals[node.attr] qual_name = f"{module_def.name}.{defn.name}" @@ -425,12 +629,7 @@ def visit_Attribute(self, node: ast.Attribute) -> tuple[ast.expr, Type]: func.ty.params, ) return with_loc(node, PartialApply(func=name, args=[node.value])), result_ty - raise GuppyTypeError( - f"Expression of type `{ty}` has no attribute `{node.attr}`", - # Unfortunately, `node.attr` doesn't contain source annotations, so we have - # to use `node` as the error location - node, - ) + raise GuppyTypeError(AttributeNotFoundError(attr_span, ty, node.attr)) def _is_module_def(self, node: ast.expr) -> ModuleDef | None: """Checks whether an AST node corresponds to a defined module.""" @@ -449,9 +648,8 @@ def visit_Tuple(self, node: ast.Tuple) -> tuple[ast.expr, Type]: def visit_List(self, node: ast.List) -> tuple[ast.expr, Type]: check_lists_enabled(node) if len(node.elts) == 0: - raise GuppyTypeInferenceError( - "Cannot infer type variable in expression of type `list[?T]`", node - ) + unsolved_ty = list_type(ExistentialTypeVar.fresh("T", False)) + raise GuppyTypeInferenceError(TypeInferenceError(node, unsolved_ty)) node.elts[0], el_ty = self.synthesize(node.elts[0]) node.elts[1:] = [self._check(el, el_ty)[0] for el in node.elts[1:]] return node, list_type(el_ty) @@ -476,9 +674,7 @@ def visit_UnaryOp(self, node: ast.UnaryOp) -> tuple[ast.expr, Type]: func = self.ctx.globals.get_instance_func(op_ty, op) if func is None: raise GuppyTypeError( - f"Unary operator `{display_name}` not defined for argument of type " - f" `{op_ty}`", - node.operand, + UnaryOperatorNotDefinedError(node.operand, op_ty, display_name) ) return func.synthesize_call([node.operand], node, self.ctx) @@ -491,7 +687,7 @@ def _synthesize_binary( `__radd__` on the right operand. """ if op.__class__ not in binary_table: - raise GuppyError("This binary operation is not supported by Guppy.", op) + raise GuppyTypeError(UnsupportedError(node, "Operator", singular=True)) lop, rop, display_name = binary_table[op.__class__] left_expr, left_ty = self.synthesize(left_expr) right_expr, right_ty = self.synthesize(right_expr) @@ -505,9 +701,8 @@ def _synthesize_binary( return func.synthesize_call([right_expr, left_expr], node, self.ctx) raise GuppyTypeError( - f"Binary operator `{display_name}` not defined for arguments of type " - f"`{left_ty}` and `{right_ty}`", - node, + # TODO: Is there a way to get the span of the operator? + BinaryOperatorNotDefinedError(node, left_ty, right_ty, display_name) ) def synthesize_instance_func( @@ -515,7 +710,7 @@ def synthesize_instance_func( node: ast.expr, args: list[ast.expr], func_name: str, - err: str, + description: str, exp_sig: FunctionType | None = None, give_reason: bool = False, ) -> tuple[ast.expr, Type]: @@ -531,17 +726,18 @@ def synthesize_instance_func( node, ty = self.synthesize(node) func = self.ctx.globals.get_instance_func(ty, func_name) if func is None: - reason = f" since it does not implement the `{func_name}` method" - raise GuppyTypeError( - f"Expression of type `{ty}` is {err}{reason if give_reason else ''}", - node, - ) + err = BadProtocolError(node, ty, description) + if give_reason and exp_sig is not None: + err.add_sub_diagnostic( + BadProtocolError.MethodMissing(None, func_name, exp_sig) + ) + raise GuppyTypeError(err) if exp_sig and unify(exp_sig, func.ty.unquantified()[0], {}) is None: - raise GuppyError( - f"Method `{ty}.{func_name}` has signature `{func.ty}`, but " - f"expected `{exp_sig}`", - node, + err = BadProtocolError(node, ty, description) + err.add_sub_diagnostic( + BadProtocolError.BadSignature(None, ty, func_name, exp_sig, func.ty) ) + raise GuppyError(err) return func.synthesize_call([node, *args], node, self.ctx) def visit_BinOp(self, node: ast.BinOp) -> tuple[ast.expr, Type]: @@ -572,7 +768,7 @@ def visit_Subscript(self, node: ast.Subscript) -> tuple[ast.expr, Type]: ExistentialTypeVar.fresh("Val", False), ) getitem_expr, result_ty = self.synthesize_instance_func( - node.value, [item_node], "__getitem__", "not subscriptable", exp_sig + node.value, [item_node], "__getitem__", "subscriptable", exp_sig ) # Subscripting a place is itself a place expr: ast.expr @@ -592,7 +788,7 @@ def visit_Subscript(self, node: ast.Subscript) -> tuple[ast.expr, Type]: def visit_Call(self, node: ast.Call) -> tuple[ast.expr, Type]: if len(node.keywords) > 0: - raise GuppyError("Keyword arguments are not supported", node.keywords[0]) + raise GuppyError(UnsupportedError(node.keywords[0], "Keyword arguments")) node.func, ty = self.synthesize(node.func) # First handle direct calls of user-defined functions and extension functions @@ -617,8 +813,8 @@ def visit_Call(self, node: ast.Call) -> tuple[ast.expr, Type]: ): check_function_tensors_enabled(node.func) if any(f.parametrized for f in function_elems): - raise GuppyTypeError( - "Polymorphic functions in tuples are not supported", node.func + raise GuppyError( + UnsupportedError(node.func, "Polymorphic function tensors") ) tensor_ty = function_tensor_signature(function_elems) @@ -643,7 +839,7 @@ def visit_MakeIter(self, node: MakeIter) -> tuple[ast.expr, Type]: [FuncInput(ty, flags)], ExistentialTypeVar.fresh("Iter", False) ) expr, ty = self.synthesize_instance_func( - node.value, [], "__iter__", "not iterable", exp_sig + node.value, [], "__iter__", "iterable", exp_sig, True ) # If the iterator was created by a `for` loop, we can add some extra checks to @@ -654,11 +850,9 @@ def visit_MakeIter(self, node: MakeIter) -> tuple[ast.expr, Type]: node.origin_node ) if breaks: - raise GuppyTypeError( - f"Loop over iterator with linear type `{ty}` cannot be terminated " - f"prematurely", - breaks[0], - ) + err = LinearForBreakError(breaks[0]) + err.add_sub_diagnostic(LinearForBreakError.LinearIteratorType(node, ty)) + raise GuppyTypeError(err) return expr, ty def visit_IterHasNext(self, node: IterHasNext) -> tuple[ast.expr, Type]: @@ -666,7 +860,7 @@ def visit_IterHasNext(self, node: IterHasNext) -> tuple[ast.expr, Type]: flags = InputFlags.Owned if ty.linear else InputFlags.NoFlags exp_sig = FunctionType([FuncInput(ty, flags)], TupleType([bool_type(), ty])) return self.synthesize_instance_func( - node.value, [], "__hasnext__", "not an iterator", exp_sig, True + node.value, [], "__hasnext__", "an iterator", exp_sig, True ) def visit_IterNext(self, node: IterNext) -> tuple[ast.expr, Type]: @@ -677,7 +871,7 @@ def visit_IterNext(self, node: IterNext) -> tuple[ast.expr, Type]: TupleType([ExistentialTypeVar.fresh("T", False), ty]), ) return self.synthesize_instance_func( - node.value, [], "__next__", "not an iterator", exp_sig, True + node.value, [], "__next__", "an iterator", exp_sig, True ) def visit_IterEnd(self, node: IterEnd) -> tuple[ast.expr, Type]: @@ -685,7 +879,7 @@ def visit_IterEnd(self, node: IterEnd) -> tuple[ast.expr, Type]: flags = InputFlags.Owned if ty.linear else InputFlags.NoFlags exp_sig = FunctionType([FuncInput(ty, flags)], NoneType()) return self.synthesize_instance_func( - node.value, [], "__end__", "not an iterator", exp_sig, True + node.value, [], "__end__", "an iterator", exp_sig, True ) def visit_ListComp(self, node: ast.ListComp) -> tuple[ast.expr, Type]: @@ -699,10 +893,7 @@ def visit_PyExpr(self, node: PyExpr) -> tuple[ast.expr, Type]: if ty := python_value_to_guppy_type(python_val, node, self.ctx.globals): return with_loc(node, ast.Constant(value=python_val)), ty - raise GuppyError( - f"Python expression of type `{type(python_val)}` is not supported by Guppy", - node, - ) + raise GuppyError(IllegalPyExpressionError(node.value, type(python_val))) def visit_NamedExpr(self, node: ast.NamedExpr) -> tuple[ast.expr, Type]: raise InternalGuppyError( @@ -745,20 +936,17 @@ def check_type_against( raise GuppyTypeError(TypeMismatchError(node, exp, act, kind)) # Check that we have found a valid instantiation for all params for i, v in enumerate(free_vars): + param = act.params[i].name if v not in subst: - raise GuppyTypeInferenceError( - f"Expected {kind} of type `{exp}`, got `{act}`. Couldn't infer an " - f"instantiation for parameter `{act.params[i].name}` (higher-rank " - "polymorphic types are not supported)", - node, - ) + err = TypeMismatchError(node, exp, act, kind) + err.add_sub_diagnostic(TypeMismatchError.CantInferParam(None, param)) + raise GuppyTypeInferenceError(err) if subst[v].unsolved_vars: - raise GuppyTypeError( - f"Expected {kind} of type `{exp}`, got `{act}`. Can't instantiate " - f"parameter `{act.params[i]}` with type `{subst[v]}` containing " - "free variables", - node, + err = TypeMismatchError(node, exp, act, kind) + err.add_sub_diagnostic( + TypeMismatchError.CantInstantiateFreeVars(None, param, subst[v]) ) + raise GuppyTypeError(err) inst = [subst[v].to_arg() for v in free_vars] subst = {v: t for v, t in subst.items() if v in exp.unsolved_vars} @@ -775,18 +963,26 @@ def check_type_against( return subst, [] -def check_num_args(exp: int, act: int, node: AstNode) -> None: +def check_num_args( + exp: int, act: int, node: AstNode, sig: FunctionType | None = None +) -> None: """Checks that the correct number of arguments have been passed to a function.""" - if act < exp: - raise GuppyTypeError( - f"Not enough arguments passed (expected {exp}, got {act})", node - ) - if exp < act: - if isinstance(node, ast.Call): - raise GuppyTypeError("Unexpected argument", node.args[exp]) - raise GuppyTypeError( - f"Too many arguments passed (expected {exp}, got {act})", node - ) + if exp == act: + return + span, detailed = to_span(node), False + if isinstance(node, ast.Call): + # We can construct a nicer error span if we know it's a regular call + detailed = True + if exp < act: + span = Span(to_span(node.args[exp]).start, to_span(node.args[-1]).end) + elif act > 0: + span = Span(to_span(node.args[-1]).end, to_span(node).end) + else: + span = Span(to_span(node.func).end, to_span(node).end) + err = WrongNumberOfArgsError(span, exp, act, detailed) + if sig: + err.add_sub_diagnostic(WrongNumberOfArgsError.SignatureHint(None, sig)) + raise GuppyTypeError(err) def type_check_args( @@ -802,7 +998,7 @@ def type_check_args( Checks that all unification variables can be inferred. """ assert not func_ty.parametrized - check_num_args(len(func_ty.inputs), len(inputs), node) + check_num_args(len(func_ty.inputs), len(inputs), node, func_ty) new_args: list[ast.expr] = [] for inp, func_inp in zip(inputs, func_ty.inputs, strict=True): @@ -821,9 +1017,7 @@ def type_check_args( # We also have to check that we found instantiations for all vars in the return type if not set.issubset(func_ty.output.unsolved_vars, subst.keys()): raise GuppyTypeInferenceError( - f"Cannot infer type variable in expression of type " - f"`{func_ty.output.substitute(subst)}`", - node, + TypeInferenceError(node, func_ty.output.substitute(subst)) ) return new_args, subst @@ -859,7 +1053,7 @@ def check_inout_arg_place(place: Place, ctx: Context, node: PlaceNode) -> Place: setitem_args[0], setitem_args[1:], "__setitem__", - "unable to have subscripted elements borrowed", + "able to borrow subscripted elements", exp_sig, True, ) @@ -875,7 +1069,7 @@ def synthesize_call( instantiation for the quantifiers in the function type. """ assert not func_ty.unsolved_vars - check_num_args(len(func_ty.inputs), len(args), node) + check_num_args(len(func_ty.inputs), len(args), node, func_ty) # Replace quantified variables with free unification variables and try to infer an # instantiation by checking the arguments @@ -906,7 +1100,7 @@ def check_call( expected type, and an instantiation for the quantifiers in the function type. """ assert not func_ty.unsolved_vars - check_num_args(len(func_ty.inputs), len(inputs), node) + check_num_args(len(func_ty.inputs), len(inputs), node, func_ty) # When checking, we can use the information from the expected return type to infer # some type arguments. However, this pushes errors inwards. For example, given a @@ -954,11 +1148,12 @@ def check_call( # Also make sure we found an instantiation for all free vars in the type we're # checking against if not set.issubset(ty.unsolved_vars, subst.keys()): - raise GuppyTypeInferenceError( - f"Expected expression of type `{ty}`, got " - f"`{func_ty.output.substitute(subst)}`. Couldn't infer type variables", - node, + unsolved = (subst.keys() - ty.unsolved_vars).pop() + err = TypeMismatchError(node, ty, func_ty.output.substitute(subst)) + err.add_sub_diagnostic( + TypeMismatchError.CantInferParam(None, unsolved.display_name) ) + raise GuppyTypeInferenceError(err) # Success implies that the substitution is closed assert all(not t.unsolved_vars for t in subst.values()) @@ -1006,23 +1201,11 @@ def to_bool(node: ast.expr, node_ty: Type, ctx: Context) -> tuple[ast.expr, Type """Tries to turn a node into a bool""" if is_bool_type(node_ty): return node, node_ty - - func = ctx.globals.get_instance_func(node_ty, "__bool__") - if func is None: - raise GuppyTypeError( - f"Expression of type `{node_ty}` cannot be interpreted as a `bool`", - node, - ) - - # We could check the return type against bool, but we can give a better error - # message if we synthesise and compare to bool by hand - call, return_ty = func.synthesize_call([node], node, ctx) - if not is_bool_type(return_ty): - raise GuppyTypeError( - f"`__bool__` on type `{node_ty}` returns `{return_ty}` instead of `bool`", - node, - ) - return call, return_ty + synth = ExprSynthesizer(ctx) + exp_sig = FunctionType([FuncInput(node_ty, InputFlags.Inout)], bool_type()) + return synth.synthesize_instance_func( + node, [node], "__bool__", "truthy", exp_sig, True + ) def synthesize_comprehension( @@ -1071,9 +1254,7 @@ def eval_py_expr(node: PyExpr, ctx: Context) -> Any: # The method we used for obtaining the Python variables in scope only works in # CPython (see `get_py_scope()`). if sys.implementation.name != "cpython": - raise GuppyError( - "Compile-time `py(...)` expressions are only supported in CPython", node - ) + raise GuppyError(PyExprNotCPythonError(node)) try: python_val = eval( # noqa: S307 @@ -1082,19 +1263,12 @@ def eval_py_expr(node: PyExpr, ctx: Context) -> Any: DummyEvalDict(ctx, node.value), ) except DummyEvalDict.GuppyVarUsedError as e: - raise GuppyError( - f"Guppy variable `{e.var}` cannot be accessed in a compile-time " - "`py(...)` expression", - e.node or node, - ) from None + raise GuppyError(PyExprNotStaticError(e.node or node, e.var)) from None except Exception as e: # Remove the top frame pointing to the `eval` call from the stack trace tb = e.__traceback__.tb_next if e.__traceback__ else None - raise GuppyError( - "Error occurred while evaluating Python expression:\n\n" - + "".join(traceback.format_exception(type(e), e, tb)), - node, - ) from e + tb_formatted = "".join(traceback.format_exception(type(e), e, tb)) + raise GuppyError(PyExprEvalError(node.value, tb_formatted)) from e return python_val @@ -1136,11 +1310,11 @@ def python_value_to_guppy_type(v: Any, node: ast.expr, globals: Globals) -> Type row_to_type([bool_type()] * v.n_bits), ) except ImportError: - raise GuppyError( - "Experimental pytket compatibility requires `tket2` to be" - " installed. See https://github.com/CQCL/tket2/tree/main/tket2-py", - node, - ) from None + err = Tket2NotInstalled(node) + err.add_sub_diagnostic( + Tket2NotInstalled.InstallInstruction(None) + ) + raise GuppyError(err) from None except ImportError: pass return None @@ -1167,6 +1341,6 @@ def _python_list_to_guppy_type( if ty is None: return None if (subst := unify(ty, el_ty, {})) is None: - raise GuppyError("Python list contains elements with different types", node) + raise GuppyError(PyExprIncoherentListError(node)) el_ty = el_ty.substitute(subst) return list_type(el_ty) diff --git a/guppylang/definition/function.py b/guppylang/definition/function.py index f0dbb1fc..747db20d 100644 --- a/guppylang/definition/function.py +++ b/guppylang/definition/function.py @@ -241,7 +241,13 @@ def parse_py_func(f: PyFunc, sources: SourceMap) -> tuple[ast.FunctionDef, str | defn = find_ipython_def(func_ast.name) if defn is not None: file = f"<{defn.cell_name}>" - sources.add_file(file, source) + sources.add_file(file, defn.cell_source) + else: + # If we couldn't find the defining cell, just use the source code we + # got from inspect. Line numbers will be wrong, but that's the best we + # can do. + sources.add_file(file, source) + line_offset = 1 else: file = inspect.getsourcefile(f) if file is None: diff --git a/guppylang/diagnostic.py b/guppylang/diagnostic.py index e7aae617..7263209b 100644 --- a/guppylang/diagnostic.py +++ b/guppylang/diagnostic.py @@ -223,14 +223,16 @@ def render_diagnostic(self, diag: Diagnostic) -> None: sub_diag.rendered_span_label, is_primary=False, ) - if diag.message: + if diag.rendered_message: self.buffer.append("") self.buffer += textwrap.wrap( - f"{diag.rendered_message}", self.MAX_MESSAGE_LINE_LEN + diag.rendered_message, + self.MAX_MESSAGE_LINE_LEN, + replace_whitespace=False, # Keep \n's in the message ) # Finally, render all sub-diagnostics that have a non-span message for sub_diag in diag.children: - if sub_diag.message: + if sub_diag.rendered_message: self.buffer.append("") self.buffer += textwrap.wrap( f"{self.level_str(sub_diag.level)}: {sub_diag.rendered_message}", diff --git a/tests/error/inout_errors/subscript_not_setable.err b/tests/error/inout_errors/subscript_not_setable.err index d5e29d00..6684c9f7 100644 --- a/tests/error/inout_errors/subscript_not_setable.err +++ b/tests/error/inout_errors/subscript_not_setable.err @@ -1,7 +1,12 @@ -Guppy compilation failed. Error in file $FILE:24 +Error: Not able to borrow subscripted elements (at $FILE:24:8) + | +22 | @guppy(module) +23 | def test(c: MyImmutableContainer) -> MyImmutableContainer: +24 | foo(c[0]) + | ^^^^ Expression of type `MyImmutableContainer` is not able to + | borrow subscripted elements -22: @guppy(module) -23: def test(c: MyImmutableContainer) -> MyImmutableContainer: -24: foo(c[0]) - ^^^^ -GuppyTypeError: Expression of type `MyImmutableContainer` is unable to have subscripted elements borrowed since it does not implement the `__setitem__` method +Help: Implement missing method: `__setitem__: (MyImmutableContainer, int, qubit +@owned) -> None` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/iter_errors/end_missing.err b/tests/error/iter_errors/end_missing.err index 90ad0692..1b1a62fe 100644 --- a/tests/error/iter_errors/end_missing.err +++ b/tests/error/iter_errors/end_missing.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:33 +Error: Not an iterator (at $FILE:33:13) + | +31 | @guppy(module) +32 | def test(x: MyType) -> None: +33 | for _ in x: + | ^ Expression of type `MyIter` is not an iterator -31: @guppy(module) -32: def test(x: MyType) -> None: -33: for _ in x: - ^ -GuppyTypeError: Expression of type `MyIter` is not an iterator since it does not implement the `__end__` method +Help: Implement missing method: `__end__: MyIter -> None` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/iter_errors/end_wrong_type.err b/tests/error/iter_errors/end_wrong_type.err index 635d6f84..116db466 100644 --- a/tests/error/iter_errors/end_wrong_type.err +++ b/tests/error/iter_errors/end_wrong_type.err @@ -1,7 +1,11 @@ -Guppy compilation failed. Error in file $FILE:37 +Error: Not an iterator (at $FILE:37:13) + | +35 | @guppy(module) +36 | def test(x: MyType) -> None: +37 | for _ in x: + | ^ Expression of type `MyIter` is not an iterator -35: @guppy(module) -36: def test(x: MyType) -> None: -37: for _ in x: - ^ -GuppyError: Method `MyIter.__end__` has signature `MyIter -> MyIter`, but expected `MyIter -> None` +Help: Fix signature of method `MyIter.__end__`: Expected `MyIter -> None`, got +`MyIter -> MyIter` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/iter_errors/hasnext_missing.err b/tests/error/iter_errors/hasnext_missing.err index 949b776f..cceae81b 100644 --- a/tests/error/iter_errors/hasnext_missing.err +++ b/tests/error/iter_errors/hasnext_missing.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:33 +Error: Not an iterator (at $FILE:33:13) + | +31 | @guppy(module) +32 | def test(x: MyType) -> None: +33 | for _ in x: + | ^ Expression of type `MyIter` is not an iterator -31: @guppy(module) -32: def test(x: MyType) -> None: -33: for _ in x: - ^ -GuppyTypeError: Expression of type `MyIter` is not an iterator since it does not implement the `__hasnext__` method +Help: Implement missing method: `__hasnext__: MyIter -> (bool, MyIter)` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/iter_errors/hasnext_wrong_type.err b/tests/error/iter_errors/hasnext_wrong_type.err index 234e217a..4514710b 100644 --- a/tests/error/iter_errors/hasnext_wrong_type.err +++ b/tests/error/iter_errors/hasnext_wrong_type.err @@ -1,7 +1,11 @@ -Guppy compilation failed. Error in file $FILE:37 +Error: Not an iterator (at $FILE:37:13) + | +35 | @guppy(module) +36 | def test(x: MyType) -> None: +37 | for _ in x: + | ^ Expression of type `MyIter` is not an iterator -35: @guppy(module) -36: def test(x: MyType) -> None: -37: for _ in x: - ^ -GuppyError: Method `MyIter.__hasnext__` has signature `MyIter -> bool`, but expected `MyIter -> (bool, MyIter)` +Help: Fix signature of method `MyIter.__hasnext__`: Expected `MyIter -> (bool, +MyIter)`, got `MyIter -> bool` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/iter_errors/iter_missing.err b/tests/error/iter_errors/iter_missing.err index 3e3c571d..fd8b91f3 100644 --- a/tests/error/iter_errors/iter_missing.err +++ b/tests/error/iter_errors/iter_missing.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:16 +Error: Not iterable (at $FILE:16:13) + | +14 | @guppy(module) +15 | def test(x: MyType) -> None: +16 | for _ in x: + | ^ Expression of type `MyType` is not iterable -14: @guppy(module) -15: def test(x: MyType) -> None: -16: for _ in x: - ^ -GuppyTypeError: Expression of type `MyType` is not iterable +Help: Implement missing method: `__iter__: MyType -> ?Iter` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/iter_errors/iter_wrong_type.err b/tests/error/iter_errors/iter_wrong_type.err index cc10baab..e38a6bdf 100644 --- a/tests/error/iter_errors/iter_wrong_type.err +++ b/tests/error/iter_errors/iter_wrong_type.err @@ -1,7 +1,11 @@ -Guppy compilation failed. Error in file $FILE:20 +Error: Not iterable (at $FILE:20:13) + | +18 | @guppy(module) +19 | def test(x: MyType) -> None: +20 | for _ in x: + | ^ Expression of type `MyType` is not iterable -18: @guppy(module) -19: def test(x: MyType) -> None: -20: for _ in x: - ^ -GuppyError: Method `MyType.__iter__` has signature `(MyType, int) -> MyType`, but expected `MyType -> ?Iter` +Help: Fix signature of method `MyType.__iter__`: Expected `MyType -> ?Iter`, +got `(MyType, int) -> MyType` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/iter_errors/next_missing.err b/tests/error/iter_errors/next_missing.err index 8ba51c01..8b16fbaa 100644 --- a/tests/error/iter_errors/next_missing.err +++ b/tests/error/iter_errors/next_missing.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:33 +Error: Not an iterator (at $FILE:33:13) + | +31 | @guppy(module) +32 | def test(x: MyType) -> None: +33 | for _ in x: + | ^ Expression of type `MyIter` is not an iterator -31: @guppy(module) -32: def test(x: MyType) -> None: -33: for _ in x: - ^ -GuppyTypeError: Expression of type `MyIter` is not an iterator since it does not implement the `__next__` method +Help: Implement missing method: `__next__: MyIter -> (?T, MyIter)` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/iter_errors/next_wrong_type.err b/tests/error/iter_errors/next_wrong_type.err index b9bac527..61377cd7 100644 --- a/tests/error/iter_errors/next_wrong_type.err +++ b/tests/error/iter_errors/next_wrong_type.err @@ -1,7 +1,11 @@ -Guppy compilation failed. Error in file $FILE:37 +Error: Not an iterator (at $FILE:37:13) + | +35 | @guppy(module) +36 | def test(x: MyType) -> None: +37 | for _ in x: + | ^ Expression of type `MyIter` is not an iterator -35: @guppy(module) -36: def test(x: MyType) -> None: -37: for _ in x: - ^ -GuppyError: Method `MyIter.__next__` has signature `MyIter -> (MyIter, float)`, but expected `MyIter -> (?T, MyIter)` +Help: Fix signature of method `MyIter.__next__`: Expected `MyIter -> (?T, +MyIter)`, got `MyIter -> (MyIter, float)` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/linear_errors/for_break.err b/tests/error/linear_errors/for_break.err index 889ddaa5..1b2c3074 100644 --- a/tests/error/linear_errors/for_break.err +++ b/tests/error/linear_errors/for_break.err @@ -1,7 +1,11 @@ -Guppy compilation failed. Error in file $FILE:17 +Error: Break in linear loop (at $FILE:17:12) + | +15 | rs += [q] +16 | if b: +17 | break + | ^^^^^ Early exit in linear loops is not allowed + | +14 | for q, b in qs: + | -- Iterator has linear type `list[(qubit, bool)]` -15: rs += [q] -16: if b: -17: break - ^^^^^ -GuppyTypeError: Loop over iterator with linear type `list[(qubit, bool)]` cannot be terminated prematurely +Guppy compilation failed due to 1 previous error diff --git a/tests/error/linear_errors/for_return.err b/tests/error/linear_errors/for_return.err index 840e4bfe..f0f3d218 100644 --- a/tests/error/linear_errors/for_return.err +++ b/tests/error/linear_errors/for_return.err @@ -1,7 +1,11 @@ -Guppy compilation failed. Error in file $FILE:17 +Error: Break in linear loop (at $FILE:17:12) + | +15 | rs += [q] +16 | if b: +17 | return [] + | ^^^^^^^^^ Early exit in linear loops is not allowed + | +14 | for q, b in qs: + | -- Iterator has linear type `list[(qubit, bool)]` -15: rs += [q] -16: if b: -17: return [] - ^^^^^^^^^ -GuppyTypeError: Loop over iterator with linear type `list[(qubit, bool)]` cannot be terminated prematurely +Guppy compilation failed due to 1 previous error diff --git a/tests/error/poly_errors/free_return_var.err b/tests/error/poly_errors/free_return_var.err index bd5e522c..91525870 100644 --- a/tests/error/poly_errors/free_return_var.err +++ b/tests/error/poly_errors/free_return_var.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:17 +Error: Cannot infer type (at $FILE:17:8) + | +15 | @guppy(module) +16 | def main() -> None: +17 | x = foo() + | ^^^^^ Cannot infer type variables in expression of type `?T` -15: @guppy(module) -16: def main() -> None: -17: x = foo() - ^^^^^ -GuppyTypeInferenceError: Cannot infer type variable in expression of type `?T` +Guppy compilation failed due to 1 previous error diff --git a/tests/error/poly_errors/pass_poly_free.err b/tests/error/poly_errors/pass_poly_free.err index 945cafb0..3520a444 100644 --- a/tests/error/poly_errors/pass_poly_free.err +++ b/tests/error/poly_errors/pass_poly_free.err @@ -1,7 +1,12 @@ -Guppy compilation failed. Error in file $FILE:24 +Error: Type mismatch (at $FILE:24:8) + | +22 | @guppy(module) +23 | def main() -> None: +24 | foo(bar) + | ^^^ Expected argument of type `?T -> ?T`, got `forall T. T -> + | T` -22: @guppy(module) -23: def main() -> None: -24: foo(bar) - ^^^ -GuppyTypeInferenceError: Expected argument of type `?T -> ?T`, got `forall T. T -> T`. Couldn't infer an instantiation for parameter `T` (higher-rank polymorphic types are not supported) +Note: Couldn't infer an instantiation for type variable `?T` (higher-rank +polymorphic types are not supported) + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/poly_errors/right_to_left.err b/tests/error/poly_errors/right_to_left.err index b488be7d..aabeec9c 100644 --- a/tests/error/poly_errors/right_to_left.err +++ b/tests/error/poly_errors/right_to_left.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:22 +Error: Cannot infer type (at $FILE:22:8) + | +20 | @guppy(module) +21 | def main() -> None: +22 | bar(foo(), 42) + | ^^^^^ Cannot infer type variables in expression of type `?T` -20: @guppy(module) -21: def main() -> None: -22: bar(foo(), 42) - ^^^^^ -GuppyTypeInferenceError: Cannot infer type variable in expression of type `?T` +Guppy compilation failed due to 1 previous error diff --git a/tests/error/py_errors/guppy_name1.err b/tests/error/py_errors/guppy_name1.err index 1b8d8b62..e2b02304 100644 --- a/tests/error/py_errors/guppy_name1.err +++ b/tests/error/py_errors/guppy_name1.err @@ -1,7 +1,9 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Not compile-time evaluatable (at $FILE:6:14) + | +4 | @compile_guppy +5 | def foo(x: int) -> int: +6 | return py(x + 1) + | ^ Guppy variable `x` cannot be accessed in a compile-time + | `py(...)` expression -4: @compile_guppy -5: def foo(x: int) -> int: -6: return py(x + 1) - ^ -GuppyError: Guppy variable `x` cannot be accessed in a compile-time `py(...)` expression +Guppy compilation failed due to 1 previous error diff --git a/tests/error/py_errors/guppy_name2.err b/tests/error/py_errors/guppy_name2.err index 73d2d298..22c88866 100644 --- a/tests/error/py_errors/guppy_name2.err +++ b/tests/error/py_errors/guppy_name2.err @@ -1,7 +1,9 @@ -Guppy compilation failed. Error in file $FILE:9 +Error: Not compile-time evaluatable (at $FILE:9:14) + | +7 | @compile_guppy +8 | def foo(x: int) -> int: +9 | return py(x + 1) + | ^ Guppy variable `x` cannot be accessed in a compile-time + | `py(...)` expression -7: @compile_guppy -8: def foo(x: int) -> int: -9: return py(x + 1) - ^ -GuppyError: Guppy variable `x` cannot be accessed in a compile-time `py(...)` expression +Guppy compilation failed due to 1 previous error diff --git a/tests/error/py_errors/list_different_tys.err b/tests/error/py_errors/list_different_tys.err index f39f07f0..0848122b 100644 --- a/tests/error/py_errors/list_different_tys.err +++ b/tests/error/py_errors/list_different_tys.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Unsupported list (at $FILE:6:14) + | +4 | @compile_guppy +5 | def foo() -> int: +6 | return py([1, 1.0]) + | ^^^^^^^^ List contains elements with different types -4: @compile_guppy -5: def foo() -> int: -6: return py([1, 1.0]) - ^^^^^^^^^^^^ -GuppyError: Python list contains elements with different types +Guppy compilation failed due to 1 previous error diff --git a/tests/error/py_errors/list_empty.err b/tests/error/py_errors/list_empty.err index 1663dcb1..381bd770 100644 --- a/tests/error/py_errors/list_empty.err +++ b/tests/error/py_errors/list_empty.err @@ -1,7 +1,9 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Cannot infer type (at $FILE:6:9) + | +4 | @compile_guppy +5 | def foo() -> None: +6 | xs = py([]) + | ^^^^^^ Cannot infer type variables in expression of type + | `list[?T]` -4: @compile_guppy -5: def foo() -> None: -6: xs = py([]) - ^^^^^^ -GuppyTypeError: Cannot infer type variable in expression of type `list[?T]` +Guppy compilation failed due to 1 previous error diff --git a/tests/error/py_errors/python_err.err b/tests/error/py_errors/python_err.err index 5d21c3e8..26b12b98 100644 --- a/tests/error/py_errors/python_err.err +++ b/tests/error/py_errors/python_err.err @@ -1,12 +1,15 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Python error (at $FILE:6:14) + | +4 | @compile_guppy +5 | def foo() -> int: +6 | return py(1 / 0) + | ^^^^^ Error occurred while evaluating this expression -4: @compile_guppy -5: def foo() -> int: -6: return py(1 / 0) - ^^^^^^^^^ -GuppyError: Error occurred while evaluating Python expression: +Traceback printed below: Traceback (most recent call last): - File "", line 1, in + File "", +line 1, in ZeroDivisionError: division by zero +Guppy compilation failed due to 1 previous error diff --git a/tests/error/py_errors/tket2_not_installed.err b/tests/error/py_errors/tket2_not_installed.err index c6e4eb79..18ebbe9a 100644 --- a/tests/error/py_errors/tket2_not_installed.err +++ b/tests/error/py_errors/tket2_not_installed.err @@ -1,7 +1,11 @@ -Guppy compilation failed. Error in file $FILE:16 +Error: Tket2 not installed (at $FILE:16:8) + | +14 | @guppy(module) +15 | def foo(q: qubit) -> qubit: +16 | f = py(circ) + | ^^^^^^^^ Experimental pytket compatibility requires `tket2` to be + | installed -14: @guppy(module) -15: def foo(q: qubit) -> qubit: -16: f = py(circ) - ^^^^^^^^ -GuppyError: Experimental pytket compatibility requires `tket2` to be installed. See https://github.com/CQCL/tket2/tree/main/tket2-py +Help: Install tket2: `pip install tket2` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/py_errors/unsupported.err b/tests/error/py_errors/unsupported.err index 4de7be8c..3ea9296d 100644 --- a/tests/error/py_errors/unsupported.err +++ b/tests/error/py_errors/unsupported.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Unsupported Python expression (at $FILE:6:14) + | +4 | @compile_guppy +5 | def foo() -> int: +6 | return py({1, 2, 3}) + | ^^^^^^^^^ Expression of type `` is not supported -4: @compile_guppy -5: def foo() -> int: -6: return py({1, 2, 3}) - ^^^^^^^^^^^^^ -GuppyError: Python expression of type `` is not supported by Guppy +Guppy compilation failed due to 1 previous error diff --git a/tests/error/struct_errors/constructor_missing_arg.err b/tests/error/struct_errors/constructor_missing_arg.err index cae3f3b7..5c34b5ab 100644 --- a/tests/error/struct_errors/constructor_missing_arg.err +++ b/tests/error/struct_errors/constructor_missing_arg.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:15 +Error: Not enough arguments (at $FILE:15:12) + | +13 | @guppy(module) +14 | def main() -> None: +15 | MyStruct() + | ^^ Missing argument (expected 1, got 0) -13: @guppy(module) -14: def main() -> None: -15: MyStruct() - ^^^^^^^^^^ -GuppyTypeError: Not enough arguments passed (expected 1, got 0) +Note: Function signature is `int -> MyStruct` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/struct_errors/constructor_too_many_args.err b/tests/error/struct_errors/constructor_too_many_args.err index 4483e46d..64418a88 100644 --- a/tests/error/struct_errors/constructor_too_many_args.err +++ b/tests/error/struct_errors/constructor_too_many_args.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:15 +Error: Too many arguments (at $FILE:15:16) + | +13 | @guppy(module) +14 | def main() -> None: +15 | MyStruct(1, 2, 3) + | ^^^^ Unexpected arguments (expected 1, got 3) -13: @guppy(module) -14: def main() -> None: -15: MyStruct(1, 2, 3) - ^ -GuppyTypeError: Unexpected argument +Note: Function signature is `int -> MyStruct` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/struct_errors/invalid_attribute_access.err b/tests/error/struct_errors/invalid_attribute_access.err index 5b08780c..90aae176 100644 --- a/tests/error/struct_errors/invalid_attribute_access.err +++ b/tests/error/struct_errors/invalid_attribute_access.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:15 +Error: Attribute not found (at $FILE:15:6) + | +13 | @guppy(module) +14 | def foo(s: MyStruct) -> None: +15 | s.z + | ^ Attribute `z` not found on type `MyStruct` -13: @guppy(module) -14: def foo(s: MyStruct) -> None: -15: s.z - ^^^ -GuppyTypeError: Expression of type `MyStruct` has no attribute `z` +Guppy compilation failed due to 1 previous error diff --git a/tests/error/tensor_errors/poly_tensor.err b/tests/error/tensor_errors/poly_tensor.err index c6b871e2..3a6c43bc 100644 --- a/tests/error/tensor_errors/poly_tensor.err +++ b/tests/error/tensor_errors/poly_tensor.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:14 +Error: Unsupported (at $FILE:14:11) + | +12 | @guppy(module) +13 | def main() -> int: +14 | return (foo, foo)(42, 42) + | ^^^^^^^^^^ Polymorphic function tensors are not supported -12: @guppy(module) -13: def main() -> int: -14: return (foo, foo)(42, 42) - ^^^^^^^^^^ -GuppyTypeError: Polymorphic functions in tuples are not supported +Guppy compilation failed due to 1 previous error diff --git a/tests/error/tensor_errors/too_few_args.err b/tests/error/tensor_errors/too_few_args.err index 3f63bb04..30afbc69 100644 --- a/tests/error/tensor_errors/too_few_args.err +++ b/tests/error/tensor_errors/too_few_args.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:12 +Error: Not enough arguments (at $FILE:12:24) + | +10 | @guppy(module) +11 | def main() -> int: +12 | return (foo, foo)(42) + | ^ Missing argument (expected 2, got 1) -10: @guppy(module) -11: def main() -> int: -12: return (foo, foo)(42) - ^^^^^^^^^^^^^^ -GuppyTypeError: Not enough arguments passed (expected 2, got 1) +Note: Function signature is `(int, int) -> (int, int)` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/tensor_errors/too_many_args.err b/tests/error/tensor_errors/too_many_args.err index d6e23bfe..b70c5495 100644 --- a/tests/error/tensor_errors/too_many_args.err +++ b/tests/error/tensor_errors/too_many_args.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:12 +Error: Too many arguments (at $FILE:12:28) + | +10 | @guppy(module) +11 | def main() -> int: +12 | return (foo, foo)(1, 2, 3) + | ^ Unexpected argument (expected 2, got 3) -10: @guppy(module) -11: def main() -> int: -12: return (foo, foo)(1, 2, 3) - ^ -GuppyTypeError: Unexpected argument +Note: Function signature is `(int, int) -> (int, int)` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/and_not_bool_left.err b/tests/error/type_errors/and_not_bool_left.err index e0b73d8f..e8f1154d 100644 --- a/tests/error/type_errors/and_not_bool_left.err +++ b/tests/error/type_errors/and_not_bool_left.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:12 +Error: Not truthy (at $FILE:12:11) + | +10 | @guppy(module) +11 | def foo(x: NonBool, y: bool) -> bool: +12 | return x and y + | ^ Expression of type `NonBool` is not truthy -10: @guppy(module) -11: def foo(x: NonBool, y: bool) -> bool: -12: return x and y - ^ -GuppyTypeError: Expression of type `NonBool` cannot be interpreted as a `bool` +Help: Implement missing method: `__bool__: NonBool -> bool` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/and_not_bool_right.err b/tests/error/type_errors/and_not_bool_right.err index 52340434..2cc50224 100644 --- a/tests/error/type_errors/and_not_bool_right.err +++ b/tests/error/type_errors/and_not_bool_right.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:12 +Error: Not truthy (at $FILE:12:17) + | +10 | @guppy(module) +11 | def foo(x: bool, y: NonBool) -> bool: +12 | return x and y + | ^ Expression of type `NonBool` is not truthy -10: @guppy(module) -11: def foo(x: bool, y: NonBool) -> bool: -12: return x and y - ^ -GuppyTypeError: Expression of type `NonBool` cannot be interpreted as a `bool` +Help: Implement missing method: `__bool__: NonBool -> bool` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/binary_not_arith_left.err b/tests/error/type_errors/binary_not_arith_left.err index 36bb9a57..85453b71 100644 --- a/tests/error/type_errors/binary_not_arith_left.err +++ b/tests/error/type_errors/binary_not_arith_left.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Operator not defined (at $FILE:6:11) + | +4 | @compile_guppy +5 | def foo(x: int) -> int: +6 | return (1, 1) * 4 + | ^^^^^^^^^^ Binary operator `*` not defined for `(int, int)` and `int` -4: @compile_guppy -5: def foo(x: int) -> int: -6: return (1, 1) * 4 - ^^^^^^^^^^ -GuppyTypeError: Binary operator `*` not defined for arguments of type `(int, int)` and `int` +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/binary_not_arith_right.err b/tests/error/type_errors/binary_not_arith_right.err index 7bd8ce2d..b8560ef9 100644 --- a/tests/error/type_errors/binary_not_arith_right.err +++ b/tests/error/type_errors/binary_not_arith_right.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Operator not defined (at $FILE:6:11) + | +4 | @compile_guppy +5 | def foo(x: int) -> int: +6 | return x + True + | ^^^^^^^^ Binary operator `+` not defined for `int` and `bool` -4: @compile_guppy -5: def foo(x: int) -> int: -6: return x + True - ^^^^^^^^ -GuppyTypeError: Binary operator `+` not defined for arguments of type `int` and `bool` +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/call_missing_arg.err b/tests/error/type_errors/call_missing_arg.err index 9562fd5a..a1160fd6 100644 --- a/tests/error/type_errors/call_missing_arg.err +++ b/tests/error/type_errors/call_missing_arg.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Not enough arguments (at $FILE:6:14) + | +4 | @compile_guppy +5 | def foo(x: int) -> int: +6 | return foo() + | ^^ Missing argument (expected 1, got 0) -4: @compile_guppy -5: def foo(x: int) -> int: -6: return foo() - ^^^^^ -GuppyTypeError: Not enough arguments passed (expected 1, got 0) +Note: Function signature is `int -> int` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/call_unexpected_arg.err b/tests/error/type_errors/call_unexpected_arg.err index 8fe6436d..5bcf8aad 100644 --- a/tests/error/type_errors/call_unexpected_arg.err +++ b/tests/error/type_errors/call_unexpected_arg.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Too many arguments (at $FILE:6:18) + | +4 | @compile_guppy +5 | def foo(x: int) -> int: +6 | return foo(x, x) + | ^ Unexpected argument (expected 1, got 2) -4: @compile_guppy -5: def foo(x: int) -> int: -6: return foo(x, x) - ^ -GuppyTypeError: Unexpected argument +Note: Function signature is `int -> int` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/if_expr_not_bool.err b/tests/error/type_errors/if_expr_not_bool.err index 2dfdd0d1..f47719a5 100644 --- a/tests/error/type_errors/if_expr_not_bool.err +++ b/tests/error/type_errors/if_expr_not_bool.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:12 +Error: Not truthy (at $FILE:12:16) + | +10 | @guppy(module) +11 | def foo(x: NonBool) -> int: +12 | return 1 if x else 0 + | ^ Expression of type `NonBool` is not truthy -10: @guppy(module) -11: def foo(x: NonBool) -> int: -12: return 1 if x else 0 - ^ -GuppyTypeError: Expression of type `NonBool` cannot be interpreted as a `bool` +Help: Implement missing method: `__bool__: NonBool -> bool` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/if_not_bool.err b/tests/error/type_errors/if_not_bool.err index 91f82b43..31ff6201 100644 --- a/tests/error/type_errors/if_not_bool.err +++ b/tests/error/type_errors/if_not_bool.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:12 +Error: Not truthy (at $FILE:12:7) + | +10 | @guppy(module) +11 | def foo(x: NonBool) -> int: +12 | if x: + | ^ Expression of type `NonBool` is not truthy -10: @guppy(module) -11: def foo(x: NonBool) -> int: -12: if x: - ^ -GuppyTypeError: Expression of type `NonBool` cannot be interpreted as a `bool` +Help: Implement missing method: `__bool__: NonBool -> bool` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/invert_not_int.err b/tests/error/type_errors/invert_not_int.err index a26cae58..a3084f7e 100644 --- a/tests/error/type_errors/invert_not_int.err +++ b/tests/error/type_errors/invert_not_int.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Operator not defined (at $FILE:6:12) + | +4 | @compile_guppy +5 | def foo() -> int: +6 | return ~() + | ^^ Unary operator `~` not defined for `()` -4: @compile_guppy -5: def foo() -> int: -6: return ~() - ^^ -GuppyTypeError: Unary operator `~` not defined for argument of type `()` +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/not_not_bool.err b/tests/error/type_errors/not_not_bool.err index b29d28d7..3d0214fd 100644 --- a/tests/error/type_errors/not_not_bool.err +++ b/tests/error/type_errors/not_not_bool.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:12 +Error: Not truthy (at $FILE:12:15) + | +10 | @guppy(module) +11 | def foo(x: NonBool) -> bool: +12 | return not x + | ^ Expression of type `NonBool` is not truthy -10: @guppy(module) -11: def foo(x: NonBool) -> bool: -12: return not x - ^ -GuppyTypeError: Expression of type `NonBool` cannot be interpreted as a `bool` +Help: Implement missing method: `__bool__: NonBool -> bool` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/not_subscriptable.err b/tests/error/type_errors/not_subscriptable.err index 0611a3bf..ff3aaa0c 100644 --- a/tests/error/type_errors/not_subscriptable.err +++ b/tests/error/type_errors/not_subscriptable.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:9 +Error: Not subscriptable (at $FILE:9:4) + | +7 | @guppy(module) +8 | def foo(x: int) -> None: +9 | x[0] + | ^ Expression of type `int` is not subscriptable -7: @guppy(module) -8: def foo(x: int) -> None: -9: x[0] - ^ -GuppyTypeError: Expression of type `int` is not subscriptable +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/or_not_bool_left.err b/tests/error/type_errors/or_not_bool_left.err index 76ec236c..0b317734 100644 --- a/tests/error/type_errors/or_not_bool_left.err +++ b/tests/error/type_errors/or_not_bool_left.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:12 +Error: Not truthy (at $FILE:12:11) + | +10 | @guppy(module) +11 | def foo(x: NonBool, y: bool) -> bool: +12 | return x or y + | ^ Expression of type `NonBool` is not truthy -10: @guppy(module) -11: def foo(x: NonBool, y: bool) -> bool: -12: return x or y - ^ -GuppyTypeError: Expression of type `NonBool` cannot be interpreted as a `bool` +Help: Implement missing method: `__bool__: NonBool -> bool` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/or_not_bool_right.err b/tests/error/type_errors/or_not_bool_right.err index 768f23a2..570a9e59 100644 --- a/tests/error/type_errors/or_not_bool_right.err +++ b/tests/error/type_errors/or_not_bool_right.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:12 +Error: Not truthy (at $FILE:12:16) + | +10 | @guppy(module) +11 | def foo(x: bool, y: NonBool) -> bool: +12 | return x or y + | ^ Expression of type `NonBool` is not truthy -10: @guppy(module) -11: def foo(x: bool, y: NonBool) -> bool: -12: return x or y - ^ -GuppyTypeError: Expression of type `NonBool` cannot be interpreted as a `bool` +Help: Implement missing method: `__bool__: NonBool -> bool` + +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/unary_not_arith.err b/tests/error/type_errors/unary_not_arith.err index 09ae6847..fa670ccf 100644 --- a/tests/error/type_errors/unary_not_arith.err +++ b/tests/error/type_errors/unary_not_arith.err @@ -1,7 +1,8 @@ -Guppy compilation failed. Error in file $FILE:6 +Error: Operator not defined (at $FILE:6:12) + | +4 | @compile_guppy +5 | def foo() -> int: +6 | return -() + | ^^ Unary operator `-` not defined for `()` -4: @compile_guppy -5: def foo() -> int: -6: return -() - ^^ -GuppyTypeError: Unary operator `-` not defined for argument of type `()` +Guppy compilation failed due to 1 previous error diff --git a/tests/error/type_errors/while_not_bool.err b/tests/error/type_errors/while_not_bool.err index a855ff9d..00520eb6 100644 --- a/tests/error/type_errors/while_not_bool.err +++ b/tests/error/type_errors/while_not_bool.err @@ -1,7 +1,10 @@ -Guppy compilation failed. Error in file $FILE:12 +Error: Not truthy (at $FILE:12:10) + | +10 | @guppy(module) +11 | def foo(x: NonBool) -> int: +12 | while x: + | ^ Expression of type `NonBool` is not truthy -10: @guppy(module) -11: def foo(x: NonBool) -> int: -12: while x: - ^ -GuppyTypeError: Expression of type `NonBool` cannot be interpreted as a `bool` +Help: Implement missing method: `__bool__: NonBool -> bool` + +Guppy compilation failed due to 1 previous error