Repository: hylang/hy

Issue:
Compiler crash from invalid use of `#@`
```hy
$ > hy
hy 0.19.0+36.g14b17afd using CPython(default) 3.9.1 on Linux
=> (#@ a
... b)
Traceback (most recent call last):
  File "/home/peter/Programming/hy/hy-source/venv/bin/hy", line 12, in <module>
    sys.exit(hy_main())
  File "/usr/lib/python3.9/code.py", line 232, in interact
    more = self.push(line)
  File "/usr/lib/python3.9/code.py", line 258, in push
    more = self.runsource(source, self.filename)
  File "/usr/lib/python3.9/code.py", line 63, in runsource
    code = self.compile(source, filename, symbol)
  File "/usr/lib/python3.9/codeop.py", line 178, in __call__
    return _maybe_compile(self.compiler, source, filename, symbol)
  File "/home/peter/Programming/hy/hy-source/hy/hy/errors.py", line 62, in __init__
    self.compute_lineinfo(expression, filename, source, lineno, colno)
  File "/home/peter/Programming/hy/hy-source/hy/hy/errors.py", line 94, in compute_lineinfo
    self.arrow_offset = len(self.text[0])
hy.errors.HyCompileError: Internal Compiler Bug 😱
⤷ Traceback (most recent call last):
  File "/home/peter/Programming/hy/hy-source/hy/hy/compiler.py", line 435, in compile
    ret = self.compile_atom(tree)
  File "/home/peter/Programming/hy/hy-source/hy/hy/compiler.py", line 429, in compile_atom
    return Result() + _model_compilers[type(atom)](self, atom)
  File "/home/peter/Programming/hy/hy-source/hy/hy/compiler.py", line 1778, in compile_expression
    return Result() + build_method(
  File "/home/peter/Programming/hy/hy-source/hy/hy/compiler.py", line 945, in compile_decorate_expression
    raise self._syntax_error(args[-1],
  File "/home/peter/Programming/hy/hy-source/hy/hy/compiler.py", line 454, in _syntax_error
    return HySyntaxError(message, expr, self.filename, self.source)
  File "/home/peter/Programming/hy/hy-source/hy/hy/errors.py", line 62, in __init__
    self.compute_lineinfo(expression, filename, source, lineno, colno)
  File "/home/peter/Programming/hy/hy-source/hy/hy/errors.py", line 94, in compute_lineinfo
    self.arrow_offset = len(self.text[0])
IndexError: string index out of range
```
Only seems to happen with the `#@` tag macro. Note the split line - doesn't happen otherwise.

Discussion:
Kodiologist: We don't still need the `assert attr in self.__dict__`, right? Also, you might add a comment here briefly hinting why we have to check `__dict__` attributes instead of using `hasattr`.

Instead of the explicit `raise AttributeError(f"{type(self).__name__ !r} has no attribute {attr !r}")`, is it possible to fall back to `object.__getattr__` via `super`?
scauligi: Ah crap, the assert was left over from testing. I'll remove that and rebase, as well as adding comments for why we need to check `__dict__` explicitly.
re using `super()` instead of an explicit exception: when I originally tried that I was getting "superclass has no attribute __getattr__" exceptions, but that doesn't seem to be the case anymore. I was probably doing something else wrong that has since been fixed. I'll change this to fall back via `super()`.

Patch:
diff --git a/NEWS.rst b/NEWS.rst
index 3c2aaf20..1c9dce48 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -1,5 +1,13 @@
 .. default-role:: code
 
+Unreleased
+==============================
+
+Bug Fixes
+------------------------------
+* Fixed a bug where AST nodes from macro expansion did not properly
+  receive source location info.
+
 0.20.0 (released 2021-01-25)
 ==============================
 
diff --git a/hy/models.py b/hy/models.py
index f70e05cf..495a6877 100644
--- a/hy/models.py
+++ b/hy/models.py
@@ -50,7 +50,7 @@ class HyObject(object):
     `abc` starting at the first column would have `start_column` 1 and
     `end_column` 3.
     """
-    properties = ["module", "start_line", "end_line", "start_column",
+    properties = ["module", "_start_line", "end_line", "_start_column",
                   "end_column"]
 
     def replace(self, other, recursive=False):
@@ -63,6 +63,22 @@ class HyObject(object):
 
         return self
 
+    @property
+    def start_line(self):
+        return getattr(self, "_start_line", 1)
+
+    @start_line.setter
+    def start_line(self, value):
+        self._start_line = value
+
+    @property
+    def start_column(self):
+        return getattr(self, "_start_column", 1)
+
+    @start_column.setter
+    def start_column(self, value):
+        self._start_column = value
+
     def __repr__(self):
         return "%s(%s)" % (self.__class__.__name__, super(HyObject, self).__repr__())
 
@@ -84,10 +100,6 @@ def wrap_value(x):
         raise HyWrapperError("Don't know how to wrap {!r}: {!r}".format(type(x), x))
     if isinstance(x, HyObject):
         new = new.replace(x, recursive=False)
-    if not hasattr(new, "start_column"):
-        new.start_column = 0
-    if not hasattr(new, "start_line"):
-        new.start_line = 0
     return new
 
 


Test patch:
diff --git a/tests/macros/test_macro_processor.py b/tests/macros/test_macro_processor.py
index 6dbaee78..ed2e5f95 100644
--- a/tests/macros/test_macro_processor.py
+++ b/tests/macros/test_macro_processor.py
@@ -2,13 +2,13 @@
 # This file is part of Hy, which is free software licensed under the Expat
 # license. See the LICENSE.
 
-from hy.macros import macro, macroexpand
+from hy.macros import macro, macroexpand, tag_macroexpand
 from hy.lex import tokenize
 
 from hy.models import HyString, HyList, HySymbol, HyExpression, HyFloat
 from hy.errors import HyMacroExpansionError
 
-from hy.compiler import HyASTCompiler
+from hy.compiler import HyASTCompiler, mangle
 
 import pytest
 
@@ -60,3 +60,12 @@ def test_macroexpand_nan():
    x = macroexpand(HyFloat(NaN), __name__, HyASTCompiler(__name__))
    assert type(x) is HyFloat
    assert math.isnan(x)
+
+def test_macroexpand_source_data():
+    # https://github.com/hylang/hy/issues/1944
+    ast = HyString('a')
+    ast.start_line = 3
+    ast.start_column = 5
+    bad = tag_macroexpand(mangle("@"), ast, "hy.core.macros")
+    assert bad.start_line == 3
+    assert bad.start_column == 5