Skip to content

Commit

Permalink
compiler: fix quoting of methods (fixes #423).
Browse files Browse the repository at this point in the history
whitequark committed May 9, 2016
1 parent 4c78bb4 commit 4e5d752
Showing 6 changed files with 129 additions and 45 deletions.
41 changes: 33 additions & 8 deletions artiq/compiler/embedding.py
Original file line number Diff line number Diff line change
@@ -105,13 +105,24 @@ def quote(self, value):
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)

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)

function_type = self.quote_function(value, self.expanded_from)
return asttyped.QuoteT(value=value, type=function_type, loc=loc)
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 @@ def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc):

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 @@ def _quote_rpc(self, callee, loc):

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)
70 changes: 38 additions & 32 deletions artiq/compiler/transforms/inferencer.py
Original file line number Diff line number Diff line change
@@ -92,6 +92,41 @@ def visit_AttributeT(self, node):
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 @@ def makenotes(printer, typea, typeb, loca, locb):
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 @@ def visit_CallT(self, node):
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:])
20 changes: 15 additions & 5 deletions artiq/compiler/transforms/llvm_ir_generator.py
Original file line number Diff line number Diff line change
@@ -1395,18 +1395,28 @@ def _quote(self, value, typ, path):
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)
19 changes: 19 additions & 0 deletions artiq/test/lit/embedding/error_kernel_method_no_self.py
Original file line number Diff line number Diff line change
@@ -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: <synthesized>: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
24 changes: 24 additions & 0 deletions artiq/test/lit/embedding/error_kernel_method_self_unify.py
Original file line number Diff line number Diff line change
@@ -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: <synthesized>:1: error: cannot unify <instance testbench.d> with <instance testbench.c
# CHECK-L: ${LINE:+1}: note: expanded from here
xb()

0 comments on commit 4e5d752

Please sign in to comment.