Repository: hylang/hy

Issue:
Misplaced tracebacks from some models created in the reader
```
$ echo ';;;;;;\na.b' >ex.hy
$ hy ex.hy 
Traceback (most recent call last):
  …
  File "/tmp/ex.hy", line 1, in <module>
    ;;;;;;
NameError: name 'a' is not defined
```

The error is wrongly attributed to line 1 because the reader fails to set position information for a model created during the expansion of `a.b` into `(. a b)`. Contrast with this case, which is correct:

```
$ echo ';;;;;;\n(. a b)' >ex.hy
$ hy ex.hy                     
Traceback (most recent call last):
  …
  File "/tmp/ex.hy", line 2, in <module>
    (. a b)
NameError: name 'a' is not defined
```

I believe it suffices to call `model.replace(model)` in `HyReader.fill_pos`, so that positions are fixed recursively. I just need to write a test.

This bug is more consequential than it sounds because it can crash pytest internally, when non-existent columns of the source are retrieved for a traceback. Hy's own error reporting doesn't fail so badly in such a case.

Discussion:

Patch:
diff --git a/NEWS.rst b/NEWS.rst
index a0b30a20..6c8cb58f 100644
--- a/NEWS.rst
+++ b/NEWS.rst
@@ -7,7 +7,7 @@ Bug Fixes
 ------------------------------
 * Fixed an installation failure in some situations when version lookup
   fails.
-* Fixed traceback pointing in scripts with shebangs.
+* Fixed some bugs with traceback pointing.
 * Fixed some bugs with escaping in bracket f-strings
 
 New Features
diff --git a/hy/reader/hy_reader.py b/hy/reader/hy_reader.py
index 4483db6d..985baaa5 100644
--- a/hy/reader/hy_reader.py
+++ b/hy/reader/hy_reader.py
@@ -128,7 +128,9 @@ class HyReader(Reader):
         """
         model.start_line, model.start_column = start
         model.end_line, model.end_column = self.pos
-        return model
+        return model.replace(model)
+          # `replace` will recurse into submodels and set any model
+          # positions that are still unset the same way.
 
     def read_default(self, key):
         """Default reader handler when nothing in the table matches.


Test patch:
diff --git a/tests/test_reader.py b/tests/test_reader.py
index 89a7fc7a..3ef6e38c 100644
--- a/tests/test_reader.py
+++ b/tests/test_reader.py
@@ -410,6 +410,13 @@ def test_lex_line_counting_multi_inner():
     assert inner.start_column == 5
 
 
+def test_line_counting_dotted():
+    # https://github.com/hylang/hy/issues/2422
+    x, = tokenize(";;;;;\na.b")
+    for e in (x, *x):
+        assert e.start_line == 2
+
+
 def test_dicts():
     """Ensure that we can tokenize a dict."""
     objs = tokenize("{foo bar bar baz}")