From b8ce3f85bd68adcfad95ac76cfa5875b084eaf7d Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 11 Jun 2015 03:55:06 +0300 Subject: [PATCH] Refactor error reporting in _unify to factor out custom notes. --- artiq/py2llvm/typing.py | 81 ++++++++++--------- .../py2llvm/typing/error_nonlocal_global.py | 2 +- lit-test/py2llvm/typing/error_return.py | 16 ++++ lit-test/py2llvm/typing/error_unify.py | 7 +- 4 files changed, 68 insertions(+), 38 deletions(-) create mode 100644 lit-test/py2llvm/typing/error_return.py diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 7e8a5fad9..1cfb50c20 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -111,7 +111,7 @@ class LocalExtractor(algorithm.Visitor): break if not found: diag = diagnostic.Diagnostic("error", - "can't declare name '{name}' as nonlocal: it is not bound in any outer scope", + "cannot declare name '{name}' as nonlocal: it is not bound in any outer scope", {"name": name}, loc, [node.keyword_loc]) self.engine.process(diag) @@ -132,51 +132,38 @@ class Inferencer(algorithm.Transformer): self.env_stack = [] self.function = None # currently visited function - def _unify(self, typea, typeb, loca, locb, kind='generic'): + def _unify(self, typea, typeb, loca, locb, makenotes=None): try: typea.unify(typeb) except types.UnificationError as e: printer = types.TypePrinter() - if kind == "expects": - note1 = diagnostic.Diagnostic("note", - "expression expecting an operand of type {typea}", - {"typea": printer.name(typea)}, - loca) - elif kind == "return_type" or kind == "return_type_none": - note1 = diagnostic.Diagnostic("note", - "function with return type {typea}", - {"typea": printer.name(typea)}, - loca) + if makenotes: + notes = makenotes(printer, typea, typeb, loca, locb) else: - note1 = diagnostic.Diagnostic("note", - "expression of type {typea}", - {"typea": printer.name(typea)}, - loca) - - if kind == "return_type_none": - note2 = diagnostic.Diagnostic("note", - "implied expression of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) - else: - note2 = diagnostic.Diagnostic("note", - "expression of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) + notes = [ + diagnostic.Diagnostic("note", + "expression of type {typea}", + {"typea": printer.name(typea)}, + loca), + diagnostic.Diagnostic("note", + "expression of type {typeb}", + {"typeb": printer.name(typeb)}, + locb) + ] highlights = [locb] if locb else [] if e.typea.find() == typea.find() and e.typeb.find() == typeb.find(): - diag = diagnostic.Diagnostic("fatal", + diag = diagnostic.Diagnostic("error", "cannot unify {typea} with {typeb}", {"typea": printer.name(typea), "typeb": printer.name(typeb)}, - loca, highlights, notes=[note1, note2]) + loca, highlights, notes) else: # give more detail - diag = diagnostic.Diagnostic("fatal", + diag = diagnostic.Diagnostic("error", "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", {"typea": printer.name(typea), "typeb": printer.name(typeb), "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)}, - loca, highlights, notes=[note1, note2]) + loca, highlights, notes) self.engine.process(diag) def _find_name(self, name, loc): @@ -227,12 +214,23 @@ class Inferencer(algorithm.Transformer): def visit_Return(self, node): node = self.generic_visit(node) + def makenotes(printer, typea, typeb, loca, locb): + return [ + diagnostic.Diagnostic("note", + "function with return type {typea}", + {"typea": printer.name(typea)}, + self.function.name_loc), + diagnostic.Diagnostic("note", + "a statement returning {typeb}", + {"typeb": printer.name(typeb)}, + node.loc) + ] if node.value is None: self._unify(self.function.return_type, types.TNone(), - self.function.name_loc, node.value.loc, kind="return_type_none") + self.function.name_loc, node.loc, makenotes) else: self._unify(self.function.return_type, node.value.type, - self.function.name_loc, node.value.loc, kind="return_type") + self.function.name_loc, node.value.loc, makenotes) def visit_Num(self, node): if isinstance(node.n, int): @@ -267,9 +265,20 @@ class Inferencer(algorithm.Transformer): node = self.generic_visit(node) node = asttyped.ListT(type=types.TList(), elts=node.elts, ctx=node.ctx, loc=node.loc) + def makenotes(printer, typea, typeb, loca, locb): + return [ + diagnostic.Diagnostic("note", + "a list of type {typea}", + {"typea": printer.name(node.type)}, + loca), + diagnostic.Diagnostic("note", + "a list element of type {typeb}", + {"typeb": printer.name(typeb)}, + locb) + ] for elt in node.elts: self._unify(node.type["elt"], elt.type, - node.loc, elt.loc, kind="expects") + node.loc, elt.loc, makenotes) return node def visit_Subscript(self, node): @@ -279,7 +288,7 @@ class Inferencer(algorithm.Transformer): loc=node.loc) # TODO: support more than just lists self._unify(types.TList(node.type), node.value.type, - node.loc, node.value.loc, kind="expects") + node.loc, node.value.loc) return node def visit_IfExp(self, node): @@ -380,7 +389,7 @@ class Printer(algorithm.Visitor): def main(): import sys, fileinput, os - if sys.argv[1] == '+diag': + if len(sys.argv) > 1 and sys.argv[1] == '+diag': del sys.argv[1] def process_diagnostic(diag): print("\n".join(diag.render(only_line=True))) diff --git a/lit-test/py2llvm/typing/error_nonlocal_global.py b/lit-test/py2llvm/typing/error_nonlocal_global.py index 8586d418d..1242f56db 100644 --- a/lit-test/py2llvm/typing/error_nonlocal_global.py +++ b/lit-test/py2llvm/typing/error_nonlocal_global.py @@ -2,7 +2,7 @@ # RUN: OutputCheck %s --file-to-check=%t def a(): - # CHECK-L: ${LINE:+1}: error: can't declare name 'x' as nonlocal: it is not bound in any outer scope + # CHECK-L: ${LINE:+1}: error: cannot declare name 'x' as nonlocal: it is not bound in any outer scope nonlocal x x = 1 diff --git a/lit-test/py2llvm/typing/error_return.py b/lit-test/py2llvm/typing/error_return.py new file mode 100644 index 000000000..a84fe8d22 --- /dev/null +++ b/lit-test/py2llvm/typing/error_return.py @@ -0,0 +1,16 @@ +# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: ${LINE:+2}: error: cannot unify int(width='a) with NoneType +# CHECK-L: ${LINE:+1}: note: function with return type int(width='a) +def a(): + return 1 + # CHECK-L: ${LINE:+1}: note: a statement returning NoneType + return + +# CHECK-L: ${LINE:+2}: error: cannot unify int(width='a) with list(elt='b) +# CHECK-L: ${LINE:+1}: note: function with return type int(width='a) +def b(): + return 1 + # CHECK-L: ${LINE:+1}: note: a statement returning list(elt='b) + return [] diff --git a/lit-test/py2llvm/typing/error_unify.py b/lit-test/py2llvm/typing/error_unify.py index 859e49205..9edf8d82a 100644 --- a/lit-test/py2llvm/typing/error_unify.py +++ b/lit-test/py2llvm/typing/error_unify.py @@ -4,5 +4,10 @@ a = 1 b = [] -# CHECK-L: ${LINE:+1}: fatal: cannot unify int(width='a) with list(elt='b) +# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with list(elt='b) a = b + +# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with list(elt='b) +[1, []] +# CHECK-L: note: a list of type list(elt=int(width='a)) +# CHECK-L: note: a list element of type list(elt='b)