CQCL / guppylang

Pythonic quantum-classical programming language
Apache License 2.0
18 stars 2 forks source link

Improve error reporting in jupyter notebooks #278

Open aborgna-q opened 1 week ago

aborgna-q commented 1 week ago

The error reporting hooks don't seem to work with jupyter.

For example, evaluating this code in a jupyter cell:

module = GuppyModule("mod")
#module.load(quantum) # This missing should cause an error
@guppy(module)
def q_function(q: qubit) -> qubit:
    h(q)
    return q

module.compile()

Produces this output, just printing the guppy error internals.

---------------------------------------------------------------------------
GuppyError                                Traceback (most recent call last)
Cell In[12], line 7
      4     h(q)
      5     return q
----> 7 module.compile()

File [...] in pretty_errors.<locals>.pretty_errors_wrapped(*args, **kwargs)
    153 with exception_hook(hook):
    154     try:
--> 155         return f(*args, **kwargs)
    156     except GuppyError as err:
    157         # For normal usage, this `try` block is not necessary since the
    158         # excepthook is automatically invoked when the exception (which is being
    159         # reraised below) is not handled. However, when running tests, we have
    160         # to manually invoke the hook to print the error message, since the
    161         # tests always have to capture exceptions.
    162         if _pytest_running():

File [...] in GuppyModule.compile(self)
    175 self._globals = self._globals.update_defs(type_defs)
    177 # Now, we can check all other definitions
--> 178 other_defs = self._check_defs(
    179     self._raw_defs, self._imported_globals | self._globals
    180 )
    181 self._globals = self._globals.update_defs(other_defs)
    183 # Prepare Hugr for this module

File [...] in GuppyModule._check_defs(raw_defs, globals)
    150 """Helper method to parse and check raw definitions."""
    151 raw_globals = globals | Globals(raw_defs, {}, {}, {})
    152 parsed = {
--> 153     def_id: defn.parse(raw_globals) if isinstance(defn, ParsableDef) else defn
    154     for def_id, defn in raw_defs.items()
    155 }
    156 parsed_globals = globals | Globals(parsed, {}, {}, {})
    157 return {
    158     def_id: (
    159         defn.check(parsed_globals) if isinstance(defn, CheckableDef) else defn
    160     )
    161     for def_id, defn in parsed.items()
    162 }

File [...] in RawFunctionDef.parse(self, globals)
     41 """Parses and checks the user-provided signature of the function."""
     42 func_ast = parse_py_func(self.python_func)
---> 43 ty = check_signature(func_ast, globals)
     44 if ty.parametrized:
     45     raise GuppyError(
     46         "Generic function definitions are not supported yet", func_ast
     47     )

File [...] in check_signature(func_def, globals)
    159 if inp.annotation is None:
    160     raise GuppyError("Argument type must be annotated", inp)
--> 161 ty = type_from_ast(inp.annotation, globals, param_var_mapping)
    162 input_tys.append(ty)
    163 input_names.append(inp.arg)

File [...] in type_from_ast(node, globals, param_var_mapping)
    119 """Turns an AST expression into a Guppy type."""
    120 # Parse an argument and check that it's valid for a `TypeParam`
--> 121 arg = arg_from_ast(node, globals, param_var_mapping)
    122 return _type_param.check_arg(arg, node).ty

File [...] in arg_from_ast(node, globals, param_var_mapping)
     26 x = node.id
     27 if x not in globals:
---> 28     raise GuppyError("Unknown identifier", node)
     29 match globals[x]:
     30     # Either a defined type (e.g. `int`, `bool`, ...)
     31     case TypeDef() as defn:

GuppyError: ('Unknown identifier', <ast.Name object at 0x10d143dd0>)

Ideally we should print the error diagnostics instead.