From 4e5d752951f511361ef19984b6affcd6339b93aa Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 9 May 2016 12:25:47 +0000 Subject: [PATCH] compiler: fix quoting of methods (fixes #423). --- artiq/compiler/embedding.py | 41 ++++++++--- artiq/compiler/transforms/inferencer.py | 70 ++++++++++--------- .../compiler/transforms/llvm_ir_generator.py | 20 ++++-- .../embedding/error_kernel_method_no_self.py | 19 +++++ .../error_kernel_method_self_unify.py | 24 +++++++ ...morphism.py => rpc_method_polymorphism.py} | 0 6 files changed, 129 insertions(+), 45 deletions(-) create mode 100644 artiq/test/lit/embedding/error_kernel_method_no_self.py create mode 100644 artiq/test/lit/embedding/error_kernel_method_self_unify.py rename artiq/test/lit/embedding/{method_polymorphism.py => rpc_method_polymorphism.py} (100%) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 69cd7831e..78a8ad52b 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -105,13 +105,24 @@ class ASTSynthesizer: loc=begin_loc.join(end_loc)) elif inspect.isfunction(value) or inspect.ismethod(value) or \ isinstance(value, pytypes.BuiltinFunctionType): - quote_loc = self._add('`') - repr_loc = self._add(repr(value)) - unquote_loc = self._add('`') - loc = quote_loc.join(unquote_loc) + if inspect.ismethod(value): + quoted_self = self.quote(value.__self__) + function_type = self.quote_function(value.__func__, self.expanded_from) + method_type = types.TMethod(quoted_self.type, function_type) - function_type = self.quote_function(value, self.expanded_from) - return asttyped.QuoteT(value=value, type=function_type, loc=loc) + dot_loc = self._add('.') + name_loc = self._add(value.__func__.__name__) + loc = quoted_self.loc.join(name_loc) + return asttyped.QuoteT(value=value, type=method_type, + self_loc=quoted_self.loc, loc=loc) + else: + function_type = self.quote_function(value, self.expanded_from) + + quote_loc = self._add('`') + repr_loc = self._add(repr(value)) + unquote_loc = self._add('`') + loc = quote_loc.join(unquote_loc) + return asttyped.QuoteT(value=value, type=function_type, loc=loc) else: quote_loc = self._add('`') repr_loc = self._add(repr(value)) @@ -476,6 +487,16 @@ class StitchingInferencer(Inferencer): super()._unify_attribute(result_type, value_node, attr_name, attr_loc, loc) + def visit_QuoteT(self, node): + if inspect.ismethod(node.value): + if types.is_rpc(types.get_method_function(node.type)): + return + self._unify_method_self(method_type=node.type, + attr_name=node.value.__func__.__name__, + attr_loc=None, + loc=node.loc, + self_loc=node.self_loc) + class TypedtreeHasher(algorithm.Visitor): def generic_visit(self, node): def freeze(obj): @@ -770,8 +791,12 @@ class Stitcher: if isinstance(callee, pytypes.BuiltinFunctionType): pass - elif isinstance(callee, pytypes.FunctionType): - signature = inspect.signature(callee) + elif isinstance(callee, pytypes.FunctionType) or isinstance(callee, pytypes.MethodType): + if isinstance(callee, pytypes.FunctionType): + signature = inspect.signature(callee) + else: + # inspect bug? + signature = inspect.signature(callee.__func__) if signature.return_annotation is not inspect.Signature.empty: ret_type = self._extract_annot(callee, signature.return_annotation, "return type", loc, is_syscall=False) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 3d2bb0541..4149c7392 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -92,6 +92,41 @@ class Inferencer(algorithm.Visitor): attr_name=node.attr, attr_loc=node.attr_loc, loc=node.loc) + def _unify_method_self(self, method_type, attr_name, attr_loc, loc, self_loc): + self_type = types.get_method_self(method_type) + function_type = types.get_method_function(method_type) + + if len(function_type.args) < 1: + diag = diagnostic.Diagnostic("error", + "function '{attr}{type}' of class '{class}' cannot accept a self argument", + {"attr": attr_name, "type": types.TypePrinter().name(function_type), + "class": self_type.name}, + loc) + self.engine.process(diag) + 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", + msgb, + {"attr": attr_name, + "typeb": printer.name(function_type)}, + locb) + ] + + self._unify(self_type, list(function_type.args.values())[0], + self_loc, loc, + makenotes=makenotes, + when=" while inferring the type for self argument") + 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): @@ -116,39 +151,8 @@ class Inferencer(algorithm.Visitor): attr_type = object_type.constructor.attributes[attr_name].find() if types.is_function(attr_type): # Convert to a method. - if len(attr_type.args) < 1: - diag = diagnostic.Diagnostic("error", - "function '{attr}{type}' of class '{class}' cannot accept a self argument", - {"attr": attr_name, "type": types.TypePrinter().name(attr_type), - "class": object_type.name}, - 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", - msgb, - {"attr": attr_name, - "typeb": printer.name(attr_type)}, - locb) - ] - - self._unify(object_type, list(attr_type.args.values())[0], - value_node.loc, loc, - makenotes=makenotes, - when=" while inferring the type for self argument") - attr_type = types.TMethod(object_type, attr_type) + self._unify_method_self(attr_type, attr_name, attr_loc, loc, value_node.loc) elif types.is_rpc(attr_type): # Convert to a method. We don't have to bother typechecking # the self argument, since for RPCs anything goes. @@ -894,6 +898,8 @@ class Inferencer(algorithm.Visitor): self._unify(node.type, typ.ret, node.loc, None) return + elif typ.arity() == 0: + return # error elsewhere typ_arity = typ.arity() - 1 typ_args = OrderedDict(list(typ.args.items())[1:]) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 605e22840..8340f6c82 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -1395,18 +1395,28 @@ class LLVMIRGenerator: lleltsptr = llglobal.bitcast(lleltsary.type.element.as_pointer()) llconst = ll.Constant(llty, [ll.Constant(lli32, len(llelts)), lleltsptr]) return llconst - elif types.is_rpc(typ) or types.is_function(typ): + elif types.is_rpc(typ) or types.is_c_function(typ): # RPC and C functions have no runtime representation. - # We only get down this codepath for ARTIQ Python functions when they're - # referenced from a constructor, and the value inside the constructor - # is never used. return ll.Constant(llty, ll.Undefined) + elif types.is_function(typ): + llfun = self.get_function(typ, self.function_map[value]) + llclosure = ll.Constant(self.llty_of_type(typ), [ + ll.Constant(llptr, ll.Undefined), + llfun + ]) + return llclosure + elif types.is_method(typ): + llclosure = self._quote(value.__func__, types.get_method_function(typ), + lambda: path() + ['__func__']) + llself = self._quote(value.__self__, types.get_method_self(typ), + lambda: path() + ['__self__']) + return ll.Constant(llty, [llclosure, llself]) else: print(typ) assert False def process_Quote(self, insn): - if insn.value in self.function_map: + if insn.value in self.function_map and types.is_function(insn.type): llfun = self.get_function(insn.type.find(), self.function_map[insn.value]) llclosure = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) llclosure = self.llbuilder.insert_value(llclosure, llfun, 1, name=insn.name) diff --git a/artiq/test/lit/embedding/error_kernel_method_no_self.py b/artiq/test/lit/embedding/error_kernel_method_no_self.py new file mode 100644 index 000000000..ed129fc39 --- /dev/null +++ b/artiq/test/lit/embedding/error_kernel_method_no_self.py @@ -0,0 +1,19 @@ +# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +class c: + pass +@kernel +def f(): + pass +c.f = f +x = c().f + +@kernel +def entrypoint(): + # CHECK-L: :1: error: function 'f()->NoneType delay('a)' of class 'testbench.c' cannot accept a self argument + # CHECK-L: ${LINE:+1}: note: expanded from here + x diff --git a/artiq/test/lit/embedding/error_kernel_method_self_unify.py b/artiq/test/lit/embedding/error_kernel_method_self_unify.py new file mode 100644 index 000000000..925ee1d20 --- /dev/null +++ b/artiq/test/lit/embedding/error_kernel_method_self_unify.py @@ -0,0 +1,24 @@ +# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +@kernel +def f(self): + core_log(self.x) +class c: + a = f + x = 1 +class d: + b = f + x = 2 +xa = c().a +xb = d().b + +@kernel +def entrypoint(): + xa() + # CHECK-L: :1: error: cannot unify with