diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 56674517c..910c49213 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -286,10 +286,7 @@ class StitchingInferencer(Inferencer): 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 @@ class StitchingInferencer(Inferencer): # * 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 @@ class StitchingInferencer(Inferencer): # 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 @@ class StitchingInferencer(Inferencer): 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 @@ class StitchingInferencer(Inferencer): 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 @@ class StitchingInferencer(Inferencer): 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): diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 6bc0cec3f..1cc2a30da 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -88,9 +88,14 @@ class Inferencer(algorithm.Visitor): 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 @@ class Inferencer(algorithm.Visitor): 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 @@ class Inferencer(algorithm.Visitor): 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) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 584799aca..25a8e9699 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -727,9 +727,9 @@ class TypePrinter(object): 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):