From 7c1abb25ec7c02d3d161963262dcac3d8ec46812 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 00:47:28 -0500 Subject: [PATCH] compiler.embedding: test all diagnostics. Also, unify and improve diagnostic messages. --- artiq/compiler/embedding.py | 54 +++++++++---------- artiq/compiler/testbench/embedding.py | 25 +++++++++ artiq/coredevice/comm_dummy.py | 24 +++------ artiq/coredevice/core.py | 1 - lit-test/test/embedding/ddb.pyon | 8 +++ lit-test/test/embedding/error_attr_absent.py | 16 ++++++ .../test/embedding/error_attr_conflict.py | 21 ++++++++ lit-test/test/embedding/error_attr_unify.py | 17 ++++++ .../test/embedding/error_rpc_annot_return.py | 14 +++++ .../test/embedding/error_rpc_default_unify.py | 15 ++++++ lit-test/test/embedding/error_rpc_return.py | 14 +++++ .../test/embedding/error_syscall_annot.py | 15 ++++++ .../embedding/error_syscall_annot_return.py | 15 ++++++ lit-test/test/embedding/error_syscall_arg.py | 15 ++++++ .../embedding/error_syscall_default_arg.py | 14 +++++ .../test/embedding/error_syscall_return.py | 14 +++++ 16 files changed, 236 insertions(+), 46 deletions(-) create mode 100644 artiq/compiler/testbench/embedding.py create mode 100644 lit-test/test/embedding/ddb.pyon create mode 100644 lit-test/test/embedding/error_attr_absent.py create mode 100644 lit-test/test/embedding/error_attr_conflict.py create mode 100644 lit-test/test/embedding/error_attr_unify.py create mode 100644 lit-test/test/embedding/error_rpc_annot_return.py create mode 100644 lit-test/test/embedding/error_rpc_default_unify.py create mode 100644 lit-test/test/embedding/error_rpc_return.py create mode 100644 lit-test/test/embedding/error_syscall_annot.py create mode 100644 lit-test/test/embedding/error_syscall_annot_return.py create mode 100644 lit-test/test/embedding/error_syscall_arg.py create mode 100644 lit-test/test/embedding/error_syscall_default_arg.py create mode 100644 lit-test/test/embedding/error_syscall_return.py diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index c93a49f7b..6910f185e 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -269,12 +269,11 @@ class StitchingInferencer(Inferencer): else: attributes = object_type.attributes - ast = self.quote(attr_value, None) + ast = self.quote(attr_value, object_loc.expanded_from) def proxy_diagnostic(diag): note = diagnostic.Diagnostic("note", - "expanded from here while trying to infer a type for an" - " attribute '{attr}' of a host object", + "while inferring a type for an attribute '{attr}' of a host object", {"attr": node.attr}, node.loc) diag.notes.append(note) @@ -293,10 +292,11 @@ class StitchingInferencer(Inferencer): # Does this conflict with an earlier guess? printer = types.TypePrinter() diag = diagnostic.Diagnostic("error", - "host object has an attribute of type {typea}, which is" - " different from previously inferred type {typeb}", + "host object has an attribute '{attr}' of type {typea}, which is" + " different from previously inferred type {typeb} for the same attribute", {"typea": printer.name(ast.type), - "typeb": printer.name(attributes[node.attr])}, + "typeb": printer.name(attributes[node.attr]), + "attr": node.attr}, object_loc) self.engine.process(diag) @@ -465,27 +465,23 @@ class Stitcher: source_buffer = source.Buffer(source_line, filename, line) return source.Range(source_buffer, column, column) - def _function_def_note(self, function): - return diagnostic.Diagnostic("note", - "definition of function '{function}'", - {"function": function.__name__}, - self._function_loc(function)) + def _call_site_note(self, call_loc, is_syscall): + if is_syscall: + return diagnostic.Diagnostic("note", + "in system call here", {}, + call_loc) + else: + return diagnostic.Diagnostic("note", + "in function called remotely here", {}, + call_loc) def _extract_annot(self, function, annot, kind, call_loc, is_syscall): if not isinstance(annot, types.Type): - if is_syscall: - note = diagnostic.Diagnostic("note", - "in system call here", {}, - call_loc) - else: - note = diagnostic.Diagnostic("note", - "in function called remotely here", {}, - call_loc) diag = diagnostic.Diagnostic("error", "type annotation for {kind}, '{annot}', is not an ARTIQ type", {"kind": kind, "annot": repr(annot)}, self._function_loc(function), - notes=[note]) + notes=[self._call_site_note(call_loc, is_syscall)]) self.engine.process(diag) return types.TVar() @@ -503,7 +499,8 @@ class Stitcher: diag = diagnostic.Diagnostic("error", "system call argument '{argument}' must have a type annotation", {"argument": param.name}, - self._function_loc(function)) + self._function_loc(function), + notes=[self._call_site_note(loc, is_syscall)]) self.engine.process(diag) elif param.default is not inspect.Parameter.empty: # Try and infer the type from the default value. @@ -517,10 +514,10 @@ class Stitcher: "expanded from here while trying to infer a type for an" " unannotated optional argument '{argument}' from its default value", {"argument": param.name}, - loc) + self._function_loc(function)) diag.notes.append(note) - diag.notes.append(self._function_def_note(function)) + diag.notes.append(self._call_site_note(loc, is_syscall)) self.engine.process(diag) @@ -556,12 +553,13 @@ class Stitcher: is_syscall=syscall is not None) elif syscall is None: optarg_types[param.name] = self._type_of_param(function, loc, param, - is_syscall=syscall is not None) + is_syscall=False) else: diag = diagnostic.Diagnostic("error", "system call argument '{argument}' must not have a default value", {"argument": param.name}, - self._function_loc(function)) + self._function_loc(function), + notes=[self._call_site_note(loc, is_syscall=True)]) self.engine.process(diag) if signature.return_annotation is not inspect.Signature.empty: @@ -570,13 +568,15 @@ class Stitcher: elif syscall is None: diag = diagnostic.Diagnostic("error", "function must have a return type annotation to be called remotely", {}, - self._function_loc(function)) + self._function_loc(function), + notes=[self._call_site_note(loc, is_syscall=False)]) self.engine.process(diag) ret_type = types.TVar() else: # syscall is not None diag = diagnostic.Diagnostic("error", "system call must have a return type annotation", {}, - self._function_loc(function)) + self._function_loc(function), + notes=[self._call_site_note(loc, is_syscall=True)]) self.engine.process(diag) ret_type = types.TVar() diff --git a/artiq/compiler/testbench/embedding.py b/artiq/compiler/testbench/embedding.py new file mode 100644 index 000000000..432638f19 --- /dev/null +++ b/artiq/compiler/testbench/embedding.py @@ -0,0 +1,25 @@ +import sys, os + +from artiq.protocols.file_db import FlatFileDB +from artiq.master.worker_db import DeviceManager + +from artiq.coredevice.core import Core, CompileError + +def main(): + with open(sys.argv[1]) as f: + testcase_code = compile(f.read(), f.name, "exec") + testcase_vars = {} + exec(testcase_code, testcase_vars) + + ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "ddb.pyon") + + try: + core = Core(dmgr=DeviceManager(FlatFileDB(ddb_path))) + core.run(testcase_vars["entrypoint"], (), {}) + print(core.comm.get_log()) + core.comm.clear_log() + except CompileError as error: + print("\n".join(error.__cause__.diagnostic.render(only_line=True))) + +if __name__ == "__main__": + main() diff --git a/artiq/coredevice/comm_dummy.py b/artiq/coredevice/comm_dummy.py index 072c9bdd8..82a1a9575 100644 --- a/artiq/coredevice/comm_dummy.py +++ b/artiq/coredevice/comm_dummy.py @@ -8,23 +8,11 @@ class Comm: def switch_clock(self, external): pass - def load(self, kcode): - print("================") - print(" LLVM IR") - print("================") - print(kcode) + def load(self, kernel_library): + pass - def run(self, kname): - print("RUN: "+kname) + def run(self): + pass - def serve(self, rpc_map, exception_map): - print("================") - print(" RPC map") - print("================") - for k, v in sorted(rpc_map.items(), key=itemgetter(0)): - print(str(k)+" -> "+str(v)) - print("================") - print(" Exception map") - print("================") - for k, v in sorted(exception_map.items(), key=itemgetter(0)): - print(str(k)+" -> "+str(v)) + def serve(self, object_map, symbolizer): + pass diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index e02b1ca47..18cc981c3 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -48,7 +48,6 @@ class Core: return stitcher.object_map, stripped_library, \ lambda addresses: target.symbolize(library, addresses) except diagnostic.Error as error: - print("\n".join(error.diagnostic.render(colored=True)), file=sys.stderr) raise CompileError() from error def run(self, function, args, kwargs): diff --git a/lit-test/test/embedding/ddb.pyon b/lit-test/test/embedding/ddb.pyon new file mode 100644 index 000000000..7c1bb62ef --- /dev/null +++ b/lit-test/test/embedding/ddb.pyon @@ -0,0 +1,8 @@ +{ + "comm": { + "type": "local", + "module": "artiq.coredevice.comm_dummy", + "class": "Comm", + "arguments": {} + } +} diff --git a/lit-test/test/embedding/error_attr_absent.py b/lit-test/test/embedding/error_attr_absent.py new file mode 100644 index 000000000..296ed860a --- /dev/null +++ b/lit-test/test/embedding/error_attr_absent.py @@ -0,0 +1,16 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +class c: + pass + +@kernel +def entrypoint(): + # CHECK-L: :1: error: host object does not have an attribute 'x' + # CHECK-L: ${LINE:+1}: note: expanded from here + a = c + # CHECK-L: ${LINE:+1}: note: attribute accessed here + a.x diff --git a/lit-test/test/embedding/error_attr_conflict.py b/lit-test/test/embedding/error_attr_conflict.py new file mode 100644 index 000000000..a5c960d5c --- /dev/null +++ b/lit-test/test/embedding/error_attr_conflict.py @@ -0,0 +1,21 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +class c: + pass + +i1 = c() +i1.x = 1 + +i2 = c() +i2.x = 1.0 + +@kernel +def entrypoint(): + # CHECK-L: :1: error: host object has an attribute 'x' of type float, which is different from previously inferred type int(width=32) for the same attribute + i1.x + # CHECK-L: ${LINE:+1}: note: expanded from here + i2.x diff --git a/lit-test/test/embedding/error_attr_unify.py b/lit-test/test/embedding/error_attr_unify.py new file mode 100644 index 000000000..e891ec73b --- /dev/null +++ b/lit-test/test/embedding/error_attr_unify.py @@ -0,0 +1,17 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +class c: + x = [1, "x"] + +@kernel +def entrypoint(): + # CHECK-L: :1: error: cannot unify int(width='a) with str + # CHECK-NEXT-L: [1, 'x'] + # CHECK-L: ${LINE:+1}: note: expanded from here + a = c + # CHECK-L: ${LINE:+1}: note: while inferring a type for an attribute 'x' of a host object + a.x diff --git a/lit-test/test/embedding/error_rpc_annot_return.py b/lit-test/test/embedding/error_rpc_annot_return.py new file mode 100644 index 000000000..063ac6c28 --- /dev/null +++ b/lit-test/test/embedding/error_rpc_annot_return.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: ${LINE:+1}: error: type annotation for return type, '1', is not an ARTIQ type +def foo() -> 1: + pass + +@kernel +def entrypoint(): + # CHECK-L: ${LINE:+1}: note: in function called remotely here + foo() diff --git a/lit-test/test/embedding/error_rpc_default_unify.py b/lit-test/test/embedding/error_rpc_default_unify.py new file mode 100644 index 000000000..9cb483628 --- /dev/null +++ b/lit-test/test/embedding/error_rpc_default_unify.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: :1: error: cannot unify int(width='a) with str +# CHECK-L: ${LINE:+1}: note: expanded from here while trying to infer a type for an unannotated optional argument 'x' from its default value +def foo(x=[1,"x"]): + pass + +@kernel +def entrypoint(): + # CHECK-L: ${LINE:+1}: note: in function called remotely here + foo() diff --git a/lit-test/test/embedding/error_rpc_return.py b/lit-test/test/embedding/error_rpc_return.py new file mode 100644 index 000000000..f3f8ddd70 --- /dev/null +++ b/lit-test/test/embedding/error_rpc_return.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: ${LINE:+1}: error: function must have a return type annotation to be called remotely +def foo(): + pass + +@kernel +def entrypoint(): + # CHECK-L: ${LINE:+1}: note: in function called remotely here + foo() diff --git a/lit-test/test/embedding/error_syscall_annot.py b/lit-test/test/embedding/error_syscall_annot.py new file mode 100644 index 000000000..cbb5aab22 --- /dev/null +++ b/lit-test/test/embedding/error_syscall_annot.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: ${LINE:+2}: error: type annotation for argument 'x', '1', is not an ARTIQ type +@syscall +def foo(x: 1) -> TNone: + pass + +@kernel +def entrypoint(): + # CHECK-L: ${LINE:+1}: note: in system call here + foo() diff --git a/lit-test/test/embedding/error_syscall_annot_return.py b/lit-test/test/embedding/error_syscall_annot_return.py new file mode 100644 index 000000000..20f1d4ac5 --- /dev/null +++ b/lit-test/test/embedding/error_syscall_annot_return.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: ${LINE:+2}: error: type annotation for return type, '1', is not an ARTIQ type +@syscall +def foo() -> 1: + pass + +@kernel +def entrypoint(): + # CHECK-L: ${LINE:+1}: note: in system call here + foo() diff --git a/lit-test/test/embedding/error_syscall_arg.py b/lit-test/test/embedding/error_syscall_arg.py new file mode 100644 index 000000000..b944cc0e9 --- /dev/null +++ b/lit-test/test/embedding/error_syscall_arg.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: ${LINE:+2}: error: system call argument 'x' must have a type annotation +@syscall +def foo(x) -> TNone: + pass + +@kernel +def entrypoint(): + # CHECK-L: ${LINE:+1}: note: in system call here + foo() diff --git a/lit-test/test/embedding/error_syscall_default_arg.py b/lit-test/test/embedding/error_syscall_default_arg.py new file mode 100644 index 000000000..df025ff19 --- /dev/null +++ b/lit-test/test/embedding/error_syscall_default_arg.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: ${LINE:+2}: error: system call argument 'x' must not have a default value +@syscall +def foo(x=1) -> TNone: + pass + +@kernel +def entrypoint(): + foo() diff --git a/lit-test/test/embedding/error_syscall_return.py b/lit-test/test/embedding/error_syscall_return.py new file mode 100644 index 000000000..c82db063d --- /dev/null +++ b/lit-test/test/embedding/error_syscall_return.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: ${LINE:+2}: error: system call must have a return type annotation +@syscall +def foo(): + pass + +@kernel +def entrypoint(): + foo()