Skip to content

Commit

Permalink
Merge pull request #2556 from Kodiologist/misplaced-tracebacks
Browse files Browse the repository at this point in the history
Fix some traceback positions
  • Loading branch information
Kodiologist authored Mar 10, 2024
2 parents f6f00bf + 26e983e commit 83d71b6
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 8 deletions.
4 changes: 4 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ New Features
* You can now set `repl-ps1` and `repl-ps2` in your `HYSTARTUP` to customize
`sys.ps1` and `sys.ps2` for the Hy REPL.

Bug Fixes
------------------------------
* Tracebacks now point to the correct code in more cases.

0.28.0 (released 2024-01-05)
=============================

Expand Down
28 changes: 27 additions & 1 deletion docs/semantics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ Semantics
==============

This chapter describes features of Hy semantics that differ from Python's and
aren't better categorized elsewhere, such as in the chapter :doc:`macros`.
aren't better categorized elsewhere, such as in the chapter :doc:`macros`. Each
is a potential trap for the unwary.

.. _implicit-names:

Expand Down Expand Up @@ -71,3 +72,28 @@ following:
Why didn't the second run of ``b.hy`` print ``2``? Because ``b.hy`` was
unchanged, so it didn't get recompiled, so its bytecode still had the old
expansion of the macro ``m``.

Traceback positioning
---------------------

When an exception results in a traceback, Python uses line and column numbers
associated with AST nodes to point to the source code associated with the
exception:

.. code-block:: text
Traceback (most recent call last):
File "cinco.py", line 4, in <module>
find()
File "cinco.py", line 2, in find
print(chippy)
^^^^^^
NameError: name 'chippy' is not defined
This position information is stored as attributes of the AST nodes. Hy tries to
set these attributes appropriately so that it can also produce correctly
targeted tracebacks, but there are cases where it can't, such as when
evaluating code that was built at runtime out of explicit calls to :ref:`model
constructors <models>`. Python still requires line and column numbers, so Hy
sets these to 1 as a fallback; consequently, tracebacks can point to the
beginning of a file even though the relevant code isn't there.
2 changes: 1 addition & 1 deletion hy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ def compile(self, tree):
# These are unexpected errors that will--hopefully--never be seen
# by the user.
f_exc = traceback.format_exc()
exc_msg = "Internal Compiler Bug 😱\n {}".format(f_exc)
exc_msg = "Internal Compiler Bug\n {}".format(f_exc)
raise HyCompileError(exc_msg)

def _syntax_error(self, expr, message):
Expand Down
2 changes: 1 addition & 1 deletion hy/core/result_macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def compile_eval_foo_compile(compiler, expr, root, body):
raise HyEvalError(str(e), compiler.filename, body, compiler.source)

return (
compiler.compile(as_model(value))
compiler.compile(as_model(value).replace(expr))
if root == "do-mac"
else compiler._compile_branch(body)
if root == "eval-and-compile"
Expand Down
21 changes: 16 additions & 5 deletions hy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,12 +385,19 @@ class Sequence(Object, tuple):
for this purpose.
"""

_extra_kwargs = ()

def replace(self, other, recursive=True):
if recursive:
for x in self:
replace_hy_obj(x, other)
Object.replace(self, other)
return self
return (
Object.replace(
Object.replace(
type(self)(
(replace_hy_obj(x, other) for x in self),
**{k: getattr(self, k) for k in self._extra_kwargs}),
self),
other)
if recursive
else Object.replace(self, other))

def __add__(self, other):
return self.__class__(
Expand Down Expand Up @@ -431,6 +438,8 @@ class FComponent(Sequence):
format spec (if any).
"""

_extra_kwargs = ("conversion",)

def __new__(cls, s=None, conversion=None):
value = super().__new__(cls, s)
value.conversion = conversion
Expand Down Expand Up @@ -465,6 +474,8 @@ class FString(Sequence):
:ivar brackets: As in :class:`hy.models.String`.
"""

_extra_kwargs = ("brackets",)

def __new__(cls, s=None, brackets=None):
value = super().__new__(
cls,
Expand Down
27 changes: 27 additions & 0 deletions tests/test_positions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'''Check that positioning attributes for AST nodes (which Python
ultimately uses for tracebacks) are set correctly.'''

import ast
from hy import read_many
from hy.compiler import hy_compile


def cpl(string):
'''Compile the Hy `string` and get its final body element. A
newline is prepended so that line 1 is guaranteed to be the wrong
position for generated nodes.'''
return hy_compile(read_many('\n' + string), __name__).body[-1]


def test_do_mac():
# https://github.com/hylang/hy/issues/2424
x = cpl("(do-mac '9)")
assert isinstance(x, ast.Expr)
assert x.lineno == 2


def test_defmacro_raise():
# https://github.com/hylang/hy/issues/2424
x = cpl("(defmacro m [] '(do (raise)))\n(m)")
assert isinstance(x, ast.Raise)
assert x.lineno == 3

0 comments on commit 83d71b6

Please sign in to comment.