Refactor error reporting in _unify to factor out custom notes.

This commit is contained in:
whitequark 2015-06-11 03:55:06 +03:00
parent ba9a7d087d
commit b8ce3f85bd
4 changed files with 68 additions and 38 deletions

View File

@ -111,7 +111,7 @@ class LocalExtractor(algorithm.Visitor):
break break
if not found: if not found:
diag = diagnostic.Diagnostic("error", 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}, {"name": name},
loc, [node.keyword_loc]) loc, [node.keyword_loc])
self.engine.process(diag) self.engine.process(diag)
@ -132,51 +132,38 @@ class Inferencer(algorithm.Transformer):
self.env_stack = [] self.env_stack = []
self.function = None # currently visited function 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: try:
typea.unify(typeb) typea.unify(typeb)
except types.UnificationError as e: except types.UnificationError as e:
printer = types.TypePrinter() printer = types.TypePrinter()
if kind == "expects": if makenotes:
note1 = diagnostic.Diagnostic("note", notes = makenotes(printer, typea, typeb, loca, locb)
"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)
else: else:
note1 = diagnostic.Diagnostic("note", notes = [
"expression of type {typea}", diagnostic.Diagnostic("note",
{"typea": printer.name(typea)}, "expression of type {typea}",
loca) {"typea": printer.name(typea)},
loca),
if kind == "return_type_none": diagnostic.Diagnostic("note",
note2 = diagnostic.Diagnostic("note", "expression of type {typeb}",
"implied expression of type {typeb}", {"typeb": printer.name(typeb)},
{"typeb": printer.name(typeb)}, locb)
locb) ]
else:
note2 = diagnostic.Diagnostic("note",
"expression of type {typeb}",
{"typeb": printer.name(typeb)},
locb)
highlights = [locb] if locb else [] highlights = [locb] if locb else []
if e.typea.find() == typea.find() and e.typeb.find() == typeb.find(): 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}", "cannot unify {typea} with {typeb}",
{"typea": printer.name(typea), "typeb": printer.name(typeb)}, {"typea": printer.name(typea), "typeb": printer.name(typeb)},
loca, highlights, notes=[note1, note2]) loca, highlights, notes)
else: # give more detail else: # give more detail
diag = diagnostic.Diagnostic("fatal", diag = diagnostic.Diagnostic("error",
"cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}",
{"typea": printer.name(typea), "typeb": printer.name(typeb), {"typea": printer.name(typea), "typeb": printer.name(typeb),
"fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)}, "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)},
loca, highlights, notes=[note1, note2]) loca, highlights, notes)
self.engine.process(diag) self.engine.process(diag)
def _find_name(self, name, loc): def _find_name(self, name, loc):
@ -227,12 +214,23 @@ class Inferencer(algorithm.Transformer):
def visit_Return(self, node): def visit_Return(self, node):
node = self.generic_visit(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: if node.value is None:
self._unify(self.function.return_type, types.TNone(), 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: else:
self._unify(self.function.return_type, node.value.type, 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): def visit_Num(self, node):
if isinstance(node.n, int): if isinstance(node.n, int):
@ -267,9 +265,20 @@ class Inferencer(algorithm.Transformer):
node = self.generic_visit(node) node = self.generic_visit(node)
node = asttyped.ListT(type=types.TList(), node = asttyped.ListT(type=types.TList(),
elts=node.elts, ctx=node.ctx, loc=node.loc) 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: for elt in node.elts:
self._unify(node.type["elt"], elt.type, self._unify(node.type["elt"], elt.type,
node.loc, elt.loc, kind="expects") node.loc, elt.loc, makenotes)
return node return node
def visit_Subscript(self, node): def visit_Subscript(self, node):
@ -279,7 +288,7 @@ class Inferencer(algorithm.Transformer):
loc=node.loc) loc=node.loc)
# TODO: support more than just lists # TODO: support more than just lists
self._unify(types.TList(node.type), node.value.type, self._unify(types.TList(node.type), node.value.type,
node.loc, node.value.loc, kind="expects") node.loc, node.value.loc)
return node return node
def visit_IfExp(self, node): def visit_IfExp(self, node):
@ -380,7 +389,7 @@ class Printer(algorithm.Visitor):
def main(): def main():
import sys, fileinput, os import sys, fileinput, os
if sys.argv[1] == '+diag': if len(sys.argv) > 1 and sys.argv[1] == '+diag':
del sys.argv[1] del sys.argv[1]
def process_diagnostic(diag): def process_diagnostic(diag):
print("\n".join(diag.render(only_line=True))) print("\n".join(diag.render(only_line=True)))

View File

@ -2,7 +2,7 @@
# RUN: OutputCheck %s --file-to-check=%t # RUN: OutputCheck %s --file-to-check=%t
def a(): 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 nonlocal x
x = 1 x = 1

View File

@ -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 []

View File

@ -4,5 +4,10 @@
a = 1 a = 1
b = [] 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 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)