Skip to content

Commit e2f6715

Browse files
authored
Add useful context information to the CompilerException class (#493)
* Add useful context information to the CompilerException class * Fix things * Be literally less dumb
1 parent 9b9f621 commit e2f6715

File tree

2 files changed

+52
-4
lines changed

2 files changed

+52
-4
lines changed

src/basilisp/lang/compiler/exception.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,20 @@
55

66
import basilisp._pyast as ast
77
import basilisp.lang.keyword as kw
8+
import basilisp.lang.map as lmap
89
from basilisp.lang.compiler.nodes import Node
9-
from basilisp.lang.interfaces import ISeq
10+
from basilisp.lang.interfaces import IExceptionInfo, IMeta, IPersistentMap, ISeq
11+
from basilisp.lang.obj import lrepr
12+
from basilisp.lang.reader import READER_COL_KW, READER_LINE_KW
1013
from basilisp.lang.typing import LispForm
1114

15+
_PHASE = kw.keyword("phase")
16+
_FORM = kw.keyword("form")
17+
_LISP_AST = kw.keyword("lisp_ast")
18+
_PY_AST = kw.keyword("py_ast")
19+
_LINE = kw.keyword("line")
20+
_COL = kw.keyword("col")
21+
1222

1323
class CompilerPhase(Enum):
1424
ANALYZING = kw.keyword("analyzing")
@@ -17,10 +27,47 @@ class CompilerPhase(Enum):
1727
COMPILING_PYTHON = kw.keyword("compiling-python")
1828

1929

20-
@attr.s(auto_attribs=True, slots=True)
21-
class CompilerException(Exception):
30+
@attr.s(auto_attribs=True, frozen=True, slots=True)
31+
class _loc:
32+
line: Optional[int] = None
33+
col: Optional[int] = None
34+
35+
def __bool__(self):
36+
return self.line is not None and self.col is not None
37+
38+
39+
@attr.s(auto_attribs=True, slots=True, str=False)
40+
class CompilerException(IExceptionInfo):
2241
msg: str
2342
phase: CompilerPhase
2443
form: Union[LispForm, None, ISeq] = None
2544
lisp_ast: Optional[Node] = None
2645
py_ast: Optional[ast.AST] = None
46+
47+
@property
48+
def data(self) -> IPersistentMap:
49+
d = {_PHASE: self.phase.value}
50+
loc = None
51+
if self.form is not None:
52+
d[_FORM] = self.form
53+
loc = (
54+
_loc(
55+
self.form.meta.val_at(READER_LINE_KW),
56+
self.form.meta.val_at(READER_COL_KW),
57+
)
58+
if isinstance(self.form, IMeta) and self.form.meta
59+
else None
60+
)
61+
if self.lisp_ast is not None: # pragma: no cover
62+
d[_LISP_AST] = self.lisp_ast
63+
loc = loc or _loc(self.lisp_ast.env.line, self.lisp_ast.env.col)
64+
if self.py_ast is not None: # pragma: no cover
65+
d[_PY_AST] = self.py_ast
66+
loc = loc or _loc(self.py_ast.lineno, self.py_ast.col_offset)
67+
if loc: # pragma: no cover
68+
d[_LINE] = loc.line
69+
d[_COL] = loc.col
70+
return lmap.map(d)
71+
72+
def __str__(self):
73+
return f"{self.msg} {lrepr(self.data)}"

tests/basilisp/cli_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ def test_compiler_error(self):
4747
result = runner.invoke(cli, ["repl"], input="(fn*)")
4848
assert (
4949
"basilisp.lang.compiler.exception.CompilerException: fn form "
50-
"must match: (fn* name? [arg*] body*) or (fn* name? method*)\nbasilisp.user=> "
50+
"must match: (fn* name? [arg*] body*) or (fn* name? method*)"
5151
) in result.stdout
52+
assert result.stdout.endswith("\nbasilisp.user=> ")
5253

5354
def test_other_exception(self):
5455
runner = CliRunner()

0 commit comments

Comments
 (0)