Skip to content

Commit 83d71b6

Browse files
authored
Merge pull request #2556 from Kodiologist/misplaced-tracebacks
Fix some traceback positions
2 parents f6f00bf + 26e983e commit 83d71b6

File tree

6 files changed

+76
-8
lines changed

6 files changed

+76
-8
lines changed

NEWS.rst

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ New Features
2020
* You can now set `repl-ps1` and `repl-ps2` in your `HYSTARTUP` to customize
2121
`sys.ps1` and `sys.ps2` for the Hy REPL.
2222

23+
Bug Fixes
24+
------------------------------
25+
* Tracebacks now point to the correct code in more cases.
26+
2327
0.28.0 (released 2024-01-05)
2428
=============================
2529

docs/semantics.rst

+27-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ Semantics
33
==============
44

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

89
.. _implicit-names:
910

@@ -71,3 +72,28 @@ following:
7172
Why didn't the second run of ``b.hy`` print ``2``? Because ``b.hy`` was
7273
unchanged, so it didn't get recompiled, so its bytecode still had the old
7374
expansion of the macro ``m``.
75+
76+
Traceback positioning
77+
---------------------
78+
79+
When an exception results in a traceback, Python uses line and column numbers
80+
associated with AST nodes to point to the source code associated with the
81+
exception:
82+
83+
.. code-block:: text
84+
85+
Traceback (most recent call last):
86+
File "cinco.py", line 4, in <module>
87+
find()
88+
File "cinco.py", line 2, in find
89+
print(chippy)
90+
^^^^^^
91+
NameError: name 'chippy' is not defined
92+
93+
This position information is stored as attributes of the AST nodes. Hy tries to
94+
set these attributes appropriately so that it can also produce correctly
95+
targeted tracebacks, but there are cases where it can't, such as when
96+
evaluating code that was built at runtime out of explicit calls to :ref:`model
97+
constructors <models>`. Python still requires line and column numbers, so Hy
98+
sets these to 1 as a fallback; consequently, tracebacks can point to the
99+
beginning of a file even though the relevant code isn't there.

hy/compiler.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ def compile(self, tree):
422422
# These are unexpected errors that will--hopefully--never be seen
423423
# by the user.
424424
f_exc = traceback.format_exc()
425-
exc_msg = "Internal Compiler Bug 😱\n {}".format(f_exc)
425+
exc_msg = "Internal Compiler Bug\n {}".format(f_exc)
426426
raise HyCompileError(exc_msg)
427427

428428
def _syntax_error(self, expr, message):

hy/core/result_macros.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def compile_eval_foo_compile(compiler, expr, root, body):
135135
raise HyEvalError(str(e), compiler.filename, body, compiler.source)
136136

137137
return (
138-
compiler.compile(as_model(value))
138+
compiler.compile(as_model(value).replace(expr))
139139
if root == "do-mac"
140140
else compiler._compile_branch(body)
141141
if root == "eval-and-compile"

hy/models.py

+16-5
Original file line numberDiff line numberDiff line change
@@ -385,12 +385,19 @@ class Sequence(Object, tuple):
385385
for this purpose.
386386
"""
387387

388+
_extra_kwargs = ()
389+
388390
def replace(self, other, recursive=True):
389-
if recursive:
390-
for x in self:
391-
replace_hy_obj(x, other)
392-
Object.replace(self, other)
393-
return self
391+
return (
392+
Object.replace(
393+
Object.replace(
394+
type(self)(
395+
(replace_hy_obj(x, other) for x in self),
396+
**{k: getattr(self, k) for k in self._extra_kwargs}),
397+
self),
398+
other)
399+
if recursive
400+
else Object.replace(self, other))
394401

395402
def __add__(self, other):
396403
return self.__class__(
@@ -431,6 +438,8 @@ class FComponent(Sequence):
431438
format spec (if any).
432439
"""
433440

441+
_extra_kwargs = ("conversion",)
442+
434443
def __new__(cls, s=None, conversion=None):
435444
value = super().__new__(cls, s)
436445
value.conversion = conversion
@@ -465,6 +474,8 @@ class FString(Sequence):
465474
:ivar brackets: As in :class:`hy.models.String`.
466475
"""
467476

477+
_extra_kwargs = ("brackets",)
478+
468479
def __new__(cls, s=None, brackets=None):
469480
value = super().__new__(
470481
cls,

tests/test_positions.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'''Check that positioning attributes for AST nodes (which Python
2+
ultimately uses for tracebacks) are set correctly.'''
3+
4+
import ast
5+
from hy import read_many
6+
from hy.compiler import hy_compile
7+
8+
9+
def cpl(string):
10+
'''Compile the Hy `string` and get its final body element. A
11+
newline is prepended so that line 1 is guaranteed to be the wrong
12+
position for generated nodes.'''
13+
return hy_compile(read_many('\n' + string), __name__).body[-1]
14+
15+
16+
def test_do_mac():
17+
# https://github.com/hylang/hy/issues/2424
18+
x = cpl("(do-mac '9)")
19+
assert isinstance(x, ast.Expr)
20+
assert x.lineno == 2
21+
22+
23+
def test_defmacro_raise():
24+
# https://github.com/hylang/hy/issues/2424
25+
x = cpl("(defmacro m [] '(do (raise)))\n(m)")
26+
assert isinstance(x, ast.Raise)
27+
assert x.lineno == 3

0 commit comments

Comments
 (0)