Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/artiq
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 3aa5acbaf3cf
Choose a base ref
...
head repository: m-labs/artiq
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 5baf18ba0d94
Choose a head ref
  • 3 commits
  • 8 files changed
  • 1 contributor

Commits on Jan 4, 2016

  1. Copy the full SHA
    05fa808 View commit details
  2. Copy the full SHA
    03dd1c3 View commit details
  3. transforms.inferencer: factor out _unify_attribute.

    whitequark committed Jan 4, 2016
    Copy the full SHA
    5baf18b View commit details
43 changes: 18 additions & 25 deletions artiq/compiler/embedding.py
Original file line number Diff line number Diff line change
@@ -286,10 +286,7 @@ def __init__(self, engine, value_map, quote):
self.value_map = value_map
self.quote = quote

def visit_AttributeT(self, node):
self.generic_visit(node)
object_type = node.value.type.find()

def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc):
# The inferencer can only observe types, not values; however,
# when we work with host objects, we have to get the values
# somewhere, since host interpreter does not have types.
@@ -304,28 +301,31 @@ def visit_AttributeT(self, node):
# * a previously unknown attribute is encountered,
# * a previously unknown host object is encountered;
# which would be the optimal solution.

object_type = value_node.type.find()
attr_value_type = None
for object_value, object_loc in self.value_map[object_type]:
if not hasattr(object_value, node.attr):
if node.attr.startswith('_'):
if not hasattr(object_value, attr_name):
if attr_name.startswith('_'):
names = set(filter(lambda name: not name.startswith('_'),
dir(object_value)))
else:
names = set(dir(object_value))
suggestion = suggest_identifier(node.attr, names)
suggestion = suggest_identifier(attr_name, names)

note = diagnostic.Diagnostic("note",
"attribute accessed here", {},
node.loc)
loc)
if suggestion is not None:
diag = diagnostic.Diagnostic("error",
"host object does not have an attribute '{attr}'; "
"did you mean '{suggestion}'?",
{"attr": node.attr, "suggestion": suggestion},
{"attr": attr_name, "suggestion": suggestion},
object_loc, notes=[note])
else:
diag = diagnostic.Diagnostic("error",
"host object does not have an attribute '{attr}'",
{"attr": node.attr},
{"attr": attr_name},
object_loc, notes=[note])
self.engine.process(diag)
return
@@ -335,7 +335,7 @@ def visit_AttributeT(self, node):
# overhead (i.e. synthesizing a source buffer), but has the advantage
# of having the host-to-ARTIQ mapping code in only one place and
# also immediately getting proper diagnostics on type errors.
attr_value = getattr(object_value, node.attr)
attr_value = getattr(object_value, attr_name)
if inspect.ismethod(attr_value) and types.is_instance(object_type):
# In cases like:
# class c:
@@ -349,8 +349,6 @@ def visit_AttributeT(self, node):
attributes = object_type.attributes
is_method = False

attr_value_type = None

if isinstance(attr_value, list):
# Fast path for lists of scalars.
IS_FLOAT = 1
@@ -387,8 +385,8 @@ def visit_AttributeT(self, node):
def proxy_diagnostic(diag):
note = diagnostic.Diagnostic("note",
"while inferring a type for an attribute '{attr}' of a host object",
{"attr": node.attr},
node.loc)
{"attr": attr_name},
loc)
diag.notes.append(note)

self.engine.process(diag)
@@ -399,31 +397,26 @@ def proxy_diagnostic(diag):
IntMonomorphizer(engine=proxy_engine).visit(ast)
attr_value_type = ast.type

if is_method and types.is_rpc_function(attr_value_type):
self_type = list(attr_value_type.args.values())[0]
self._unify(object_type, self_type,
node.loc, None)

if node.attr not in attributes:
if attr_name not in attributes:
# We just figured out what the type should be. Add it.
attributes[node.attr] = attr_value_type
attributes[attr_name] = attr_value_type
elif not types.is_rpc_function(attr_value_type):
# Does this conflict with an earlier guess?
# RPC function types are exempt because RPCs are dynamically typed.
try:
attributes[node.attr].unify(attr_value_type)
attributes[attr_name].unify(attr_value_type)
except types.UnificationError as e:
printer = types.TypePrinter()
diag = diagnostic.Diagnostic("error",
"host object has an attribute '{attr}' of type {typea}, which is"
" different from previously inferred type {typeb} for the same attribute",
{"typea": printer.name(attr_value_type),
"typeb": printer.name(attributes[node.attr]),
"typeb": printer.name(attributes[attr_name]),
"attr": node.attr},
object_loc)
self.engine.process(diag)

super().visit_AttributeT(node)
super()._unify_attribute(result_type, value_node, attr_name, attr_loc, loc)

class TypedtreeHasher(algorithm.Visitor):
def generic_visit(self, node):
65 changes: 44 additions & 21 deletions artiq/compiler/transforms/inferencer.py
Original file line number Diff line number Diff line change
@@ -88,9 +88,14 @@ def visit_ListT(self, node):

def visit_AttributeT(self, node):
self.generic_visit(node)
object_type = node.value.type.find()
self._unify_attribute(result_type=node.type, value_node=node.value,
attr_name=node.attr, attr_loc=node.attr_loc,
loc=node.loc)

def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc):
object_type = value_node.type.find()
if not types.is_var(object_type):
if node.attr in object_type.attributes:
if attr_name in object_type.attributes:
def makenotes(printer, typea, typeb, loca, locb):
return [
diagnostic.Diagnostic("note",
@@ -100,18 +105,18 @@ def makenotes(printer, typea, typeb, loca, locb):
diagnostic.Diagnostic("note",
"expression of type {typeb}",
{"typeb": printer.name(object_type)},
node.value.loc)
value_node.loc)
]

attr_type = object_type.attributes[node.attr]
attr_type = object_type.attributes[attr_name]
if types.is_rpc_function(attr_type):
attr_type = types.instantiate(attr_type)

self._unify(node.type, attr_type, node.loc, None,
makenotes=makenotes, when=" for attribute '{}'".format(node.attr))
self._unify(result_type, attr_type, loc, None,
makenotes=makenotes, when=" for attribute '{}'".format(attr_name))
elif types.is_instance(object_type) and \
node.attr in object_type.constructor.attributes:
attr_type = object_type.constructor.attributes[node.attr].find()
attr_name in object_type.constructor.attributes:
attr_type = object_type.constructor.attributes[attr_name].find()
if types.is_rpc_function(attr_type):
attr_type = types.instantiate(attr_type)

@@ -120,48 +125,54 @@ def makenotes(printer, typea, typeb, loca, locb):
if len(attr_type.args) < 1:
diag = diagnostic.Diagnostic("error",
"function '{attr}{type}' of class '{class}' cannot accept a self argument",
{"attr": node.attr, "type": types.TypePrinter().name(attr_type),
{"attr": attr_name, "type": types.TypePrinter().name(attr_type),
"class": object_type.name},
node.loc)
loc)
self.engine.process(diag)
return
else:
def makenotes(printer, typea, typeb, loca, locb):
if attr_loc is None:
msgb = "reference to an instance with a method '{attr}{typeb}'"
else:
msgb = "reference to a method '{attr}{typeb}'"

return [
diagnostic.Diagnostic("note",
"expression of type {typea}",
{"typea": printer.name(typea)},
loca),
diagnostic.Diagnostic("note",
"reference to a class function of type {typeb}",
{"typeb": printer.name(attr_type)},
msgb,
{"attr": attr_name,
"typeb": printer.name(attr_type)},
locb)
]

self._unify(object_type, list(attr_type.args.values())[0],
node.value.loc, node.loc,
value_node.loc, loc,
makenotes=makenotes,
when=" while inferring the type for self argument")

attr_type = types.TMethod(object_type, attr_type)

if not types.is_var(attr_type):
self._unify(node.type, attr_type,
node.loc, None)
self._unify(result_type, attr_type,
loc, None)
else:
if node.attr_loc.source_buffer == node.value.loc.source_buffer:
highlights, notes = [node.value.loc], []
if attr_name_loc.source_buffer == value_node.loc.source_buffer:
highlights, notes = [value_node.loc], []
else:
# This happens when the object being accessed is embedded
# from the host program.
note = diagnostic.Diagnostic("note",
"object being accessed", {},
node.value.loc)
value_node.loc)
highlights, notes = [], [note]

diag = diagnostic.Diagnostic("error",
"type {type} does not have an attribute '{attr}'",
{"type": types.TypePrinter().name(object_type), "attr": node.attr},
{"type": types.TypePrinter().name(object_type), "attr": attr_name},
node.attr_loc, highlights, notes)
self.engine.process(diag)

@@ -963,8 +974,7 @@ def visit_withitem(self, node):
self.generic_visit(node)

typ = node.context_expr.type
if not (types.is_builtin(typ, "parallel") or
types.is_builtin(typ, "sequential") or
if (types.is_builtin(typ, "parallel") or types.is_builtin(typ, "sequential") or
(isinstance(node.context_expr, asttyped.CallT) and
types.is_builtin(node.context_expr.func.type, "watchdog"))):
diag = diagnostic.Diagnostic("error",
@@ -977,6 +987,19 @@ def visit_withitem(self, node):
self._unify(node.optional_vars.type, node.context_expr.type,
node.optional_vars.loc, node.context_expr.loc)

def visit_With(self, node):
self.generic_visit(node)

for item_node in node.items:
typ = item_node.context_expr.type.find()
if (types.is_builtin(typ, "parallel") or types.is_builtin(typ, "sequential")) and \
len(node.items) != 1:
diag = diagnostic.Diagnostic("error",
"the '{kind}' context manager must be the only one in a 'with' statement",
{"kind": typ.name},
node.keyword_loc.join(node.colon_loc))
self.engine.process(diag)

def visit_ExceptHandlerT(self, node):
self.generic_visit(node)

4 changes: 2 additions & 2 deletions artiq/compiler/types.py
Original file line number Diff line number Diff line change
@@ -727,9 +727,9 @@ def name(self, typ):
signature += " " + self.name(delay)

if isinstance(typ, TRPCFunction):
return "rpc({}) {}".format(typ.service, signature)
return "[rpc #{}]{}".format(typ.service, signature)
if isinstance(typ, TCFunction):
return "ffi({}) {}".format(repr(typ.name), signature)
return "[ffi {}]{}".format(repr(typ.name), signature)
elif isinstance(typ, TFunction):
return signature
elif isinstance(typ, TBuiltinFunction):
2 changes: 1 addition & 1 deletion artiq/coredevice/core.py
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ def shorten_path(path):
return "\n".join(lines)

class _DiagnosticEngine(diagnostic.Engine):
def print_diagnostic(self, diagnostic):
def render_diagnostic(self, diagnostic):
sys.stderr.write(_render_diagnostic(diagnostic) + "\n")

class CompileError(Exception):
1 change: 0 additions & 1 deletion artiq/frontend/artiq_compile.py
Original file line number Diff line number Diff line change
@@ -54,7 +54,6 @@ def main():
core.compile(exp.run, [exp_inst], {},
with_attr_writeback=False)
except CompileError as error:
print(error.render_string(colored=True), file=sys.stderr)
return
finally:
device_mgr.close_devices()
1 change: 0 additions & 1 deletion artiq/frontend/artiq_run.py
Original file line number Diff line number Diff line change
@@ -129,7 +129,6 @@ def run(with_file=False):
exp_inst.run()
exp_inst.analyze()
except CompileError as error:
print(error.render_string(colored=True), file=sys.stderr)
return
except Exception as exn:
if hasattr(exn, 'artiq_exception'):
1 change: 1 addition & 0 deletions artiq/master/worker_impl.py
Original file line number Diff line number Diff line change
@@ -230,6 +230,7 @@ def main():
elif action == "terminate":
break
except CompileError as exc:
# TODO: This should be replaced with a proper DiagnosticEngine.
message = "Cannot compile {}\n".format(experiment_file) + exc.render_string()
if repository_path is not None:
message = message.replace(repository_path, "<repository>")
6 changes: 6 additions & 0 deletions lit-test/test/inferencer/error_with_many.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t
# RUN: OutputCheck %s --file-to-check=%t

# CHECK-L: ${LINE:+1}: error: the 'parallel' context manager must be the only one in a 'with' statement
with parallel, sequential:
pass