This PR refactors the exception/error classes and their handling, keeps Hy source strings and their corresponding file information (if any) closer to the origin of an exception (so that calling code isn't responsible for annotating exceptions), and provides minimally intrusive traceback print-out filtering via a context manager that temporarily alters sys.excepthook
(this is automatically enabled for Hy command-line sessions).
New environment and package variables have been introduced, as well:
HY_COLORED_ERRORS
, and package variable, hy.errors._hy_colored_errors
, that enables/disables manual error coloring, and
HY_COLORED_AST_OBJECTS
, and package variable, hy.models._hy_colored_ast_objects
, that enables/disables AST object coloring.
Also, source caching has been added to the Hy interpreter. It's modeled after the approach used by IPython via linecache
. Now, for example, traces will show the relevant source lines for functions, objects, etc., defined exclusively in the REPL (see the example below).
- [x] #657
- [x] #1510
- [x] #1429
- [x] #1397
- [x] #1486 (the error message part)
Notes
As mentioned above, the sys.excepthook
provided here only hides internal frames from the traceback print-out, and not the actual traceback object, so, for instance, it does not affect the stack in pdb
.
There are other, more powerful and direct approaches that involve altering and/or creating low-level traceback objects (e.g. Jinja's). These allow for a better debugging experience (e.g. without any compiler or parser frames), but they're also more complicated and distribution dependent. In the long run, I think it's better to have custom tracebacks, so I'll keep an eye on good/better ways to do this; for example, this approach is interesting.
Example
Before
hy 0.15.0+39.gd2319dc using CPython(default) 3.6.6 on Linux
=> (with [file (open "a.hy" "w")] (file.write "(print 'hi\""))
11
=> (import a)
Traceback (most recent call last):
File "/home/bwillard/projects/code/python/hy-master/hy/importer.py", line 138, in hy_eval
eval(ast_compile(_ast, "<eval_body>", "exec"), namespace)
File "<eval_body>", line 1, in <module>
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 674, in exec_module
File "<frozen importlib._bootstrap_external>", line 781, in get_code
File "/home/bwillard/projects/code/python/hy-master/hy/importer.py", line 228, in _hy_source_to_code
hy_tree = hy_parse(source)
File "/home/bwillard/projects/code/python/hy-master/hy/importer.py", line 65, in hy_parse
return HyExpression([HySymbol("do")] + tokenize(source + "\n"))
File "/home/bwillard/projects/code/python/hy-master/hy/lex/__init__.py", line 19, in tokenize
return parser.parse(lexer.lex(buf))
File "/home/bwillard/apps/anaconda3/envs/hy-master-cpython36/lib/python3.6/site-packages/rply/parser.py", line 23, in parse
t, symstack, statestack, state
File "/home/bwillard/apps/anaconda3/envs/hy-master-cpython36/lib/python3.6/site-packages/rply/parser.py", line 80, in _reduce_production
value = p.func(targ)
File "/home/bwillard/projects/code/python/hy-master/hy/lex/parser.py", line 223, in t_partial_string
raise PrematureEndOfInput("Premature end of input")
hy.lex.exceptions.PrematureEndOfInput: LexException: Premature end of input
=> (require blah)
Traceback (most recent call last):
File "/home/bwillard/projects/code/python/hy-master/hy/importer.py", line 120, in hy_eval
_ast, expr = hy_compile(hytree, module_name, get_expr=True)
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 1716, in hy_compile
result = compiler.compile(tree)
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 350, in compile
ret = self.compile_atom(tree)
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 344, in compile_atom
return Result() + _model_compilers[type(atom)](self, atom)
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 1584, in compile_expression
self, expr, unmangle(sroot), *parse_tree)
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 573, in compile_do
return self._compile_branch(body)
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 444, in _compile_branch
ret += self.compile(exprs[-1])
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 361, in compile
raise_empty(HyCompileError, e, sys.exc_info()[2])
File "<string>", line 1, in raise_empty
hy.errors.HyCompileError: Internal Compiler Bug đ±
‷ ModuleNotFoundError: No module named 'blah'
Compilation traceback:
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 350, in compile
ret = self.compile_atom(tree)
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 344, in compile_atom
return Result() + _model_compilers[type(atom)](self, atom)
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 1584, in compile_expression
self, expr, unmangle(sroot), *parse_tree)
File "/home/bwillard/projects/code/python/hy-master/hy/compiler.py", line 1154, in compile_import_or_require
importlib.import_module(module)
File "/home/bwillard/apps/anaconda3/envs/hy-master-cpython36/lib/python3.6/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 994, in _gcd_import
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 953, in _find_and_load_unlocked
=> (defn test [x]
... (print(+ 1 x))
... )
=> (test 'a)
Traceback (most recent call last):
File "/home/bwillard/projects/code/python/hy-master/hy/importer.py", line 141, in hy_eval
return eval(ast_compile(expr, "<eval>", "eval"), namespace)
File "<eval>", line 1, in <module>
File "<eval_body>", line 1, in test
TypeError: unsupported operand type(s) for +: 'int' and 'HySymbol'
=> (defmacro blah [x] `(print ~@z))
<function <lambda> at 0x7f1351095730>
=> (defmacro bleh [q] `(print (blah ~q)))
<function <lambda> at 0x7f1351095840>
=> (bleh y)
File "<input>", unknown location
HyMacroExpansionError: expanding `blah': NameError("name 'z' is not defined",)
=> (defn foo []
... (setv bar 42)
... (import pdb) (pdb.set-trace))
=> (foo)
--Return--
> <eval_body>(1)foo()->None
(Pdb) l
[EOF]
After
hy 0.15.0+45.ga8d6a48 using CPython(default) 3.6.6 on Linux
=> (with [file (open "a.hy" "w")] (file.write "(print 'hi\""))
11
=> (import a)
Traceback (most recent call last):
File "stdin-99c0d4eb813cb56c622500ab4696f5bbb3cb3660", line 1, in <module>
(import a)
File "<frozen importlib._bootstrap>", line 971, in _find_and_load
File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 674, in exec_module
File "<frozen importlib._bootstrap_external>", line 781, in get_code
File "/tmp/a.hy", line 1
(print 'hi"
^
hy.lex.exceptions.PrematureEndOfInput: Partial string literal
=> (require blah)
Traceback (most recent call last):
File "stdin-97db2eacebe610e14f46e56e428d68ec1c466f2f", line 1, in <module>
(require blah)
hy.errors.HyRequireError: No module named 'blah'
=> (defn test [x]
... (print (+ 1 x))
... )
None
=> (test 'a)
Traceback (most recent call last):
File "stdin-e199a43c50b388bff5e7901a7a1f02f5525d862d", line 1, in <module>
(test 'a)
File "stdin-71d0b2f8a864d8760f5a5eab4efa209694ee2714", line 2, in test
(print (+ 1 x))
TypeError: unsupported operand type(s) for +: 'int' and 'HySymbol'
=> (defmacro blah [x] `(print ~@z))
<function <lambda> at 0x7fd6741319d8>
=> (defmacro bleh [q] `(print (blah ~q)))
<function <lambda> at 0x7fd674131268>
=> (bleh y)
Traceback (most recent call last):
File "stdin-ff397ee5ec5c34f03703a2acccc9f62ed9b3a9ae", line 1, in <module>
(bleh y)
File "stdin-a0f7e3bfef673f0bae54abd3721c76c25db16c65", line 1, in <lambda>
(defmacro blah [x] `(print ~@z))
hy.errors.HyMacroExpansionError: expanding macro blah
NameError: name 'z' is not defined
=> (defn foo []
... (setv bar 42)
... (import pdb) (pdb.set-trace))
None
=> (foo)
--Return--
> /home/bwillard/stdin-2741f77da2a8503d126c87cd48d527a65903cc14(3)foo()->None
(Pdb) l
1 (defn foo []
2 (setv bar 42)
3 -> (import pdb) (pdb.set-trace))
[EOF]