compiler: fix quoting of methods (fixes #423).

This commit is contained in:
whitequark 2016-05-09 12:25:47 +00:00
parent 4c78bb4950
commit 4e5d752951
6 changed files with 129 additions and 45 deletions

View File

@ -105,13 +105,24 @@ class ASTSynthesizer:
loc=begin_loc.join(end_loc)) loc=begin_loc.join(end_loc))
elif inspect.isfunction(value) or inspect.ismethod(value) or \ elif inspect.isfunction(value) or inspect.ismethod(value) or \
isinstance(value, pytypes.BuiltinFunctionType): isinstance(value, pytypes.BuiltinFunctionType):
quote_loc = self._add('`') if inspect.ismethod(value):
repr_loc = self._add(repr(value)) quoted_self = self.quote(value.__self__)
unquote_loc = self._add('`') function_type = self.quote_function(value.__func__, self.expanded_from)
loc = quote_loc.join(unquote_loc) method_type = types.TMethod(quoted_self.type, function_type)
function_type = self.quote_function(value, self.expanded_from) dot_loc = self._add('.')
return asttyped.QuoteT(value=value, type=function_type, loc=loc) 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: else:
quote_loc = self._add('`') quote_loc = self._add('`')
repr_loc = self._add(repr(value)) 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) 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): class TypedtreeHasher(algorithm.Visitor):
def generic_visit(self, node): def generic_visit(self, node):
def freeze(obj): def freeze(obj):
@ -770,8 +791,12 @@ class Stitcher:
if isinstance(callee, pytypes.BuiltinFunctionType): if isinstance(callee, pytypes.BuiltinFunctionType):
pass pass
elif isinstance(callee, pytypes.FunctionType): elif isinstance(callee, pytypes.FunctionType) or isinstance(callee, pytypes.MethodType):
signature = inspect.signature(callee) 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: if signature.return_annotation is not inspect.Signature.empty:
ret_type = self._extract_annot(callee, signature.return_annotation, ret_type = self._extract_annot(callee, signature.return_annotation,
"return type", loc, is_syscall=False) "return type", loc, is_syscall=False)

View File

@ -92,6 +92,41 @@ class Inferencer(algorithm.Visitor):
attr_name=node.attr, attr_loc=node.attr_loc, attr_name=node.attr, attr_loc=node.attr_loc,
loc=node.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): def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc):
object_type = value_node.type.find() object_type = value_node.type.find()
if not types.is_var(object_type): if not types.is_var(object_type):
@ -116,39 +151,8 @@ class Inferencer(algorithm.Visitor):
attr_type = object_type.constructor.attributes[attr_name].find() attr_type = object_type.constructor.attributes[attr_name].find()
if types.is_function(attr_type): if types.is_function(attr_type):
# Convert to a method. # 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) 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): elif types.is_rpc(attr_type):
# Convert to a method. We don't have to bother typechecking # Convert to a method. We don't have to bother typechecking
# the self argument, since for RPCs anything goes. # the self argument, since for RPCs anything goes.
@ -894,6 +898,8 @@ class Inferencer(algorithm.Visitor):
self._unify(node.type, typ.ret, self._unify(node.type, typ.ret,
node.loc, None) node.loc, None)
return return
elif typ.arity() == 0:
return # error elsewhere
typ_arity = typ.arity() - 1 typ_arity = typ.arity() - 1
typ_args = OrderedDict(list(typ.args.items())[1:]) typ_args = OrderedDict(list(typ.args.items())[1:])

View File

@ -1395,18 +1395,28 @@ class LLVMIRGenerator:
lleltsptr = llglobal.bitcast(lleltsary.type.element.as_pointer()) lleltsptr = llglobal.bitcast(lleltsary.type.element.as_pointer())
llconst = ll.Constant(llty, [ll.Constant(lli32, len(llelts)), lleltsptr]) llconst = ll.Constant(llty, [ll.Constant(lli32, len(llelts)), lleltsptr])
return llconst 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. # 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) 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: else:
print(typ) print(typ)
assert False assert False
def process_Quote(self, insn): 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]) llfun = self.get_function(insn.type.find(), self.function_map[insn.value])
llclosure = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) llclosure = ll.Constant(self.llty_of_type(insn.type), ll.Undefined)
llclosure = self.llbuilder.insert_value(llclosure, llfun, 1, name=insn.name) llclosure = self.llbuilder.insert_value(llclosure, llfun, 1, name=insn.name)

View File

@ -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

View File

@ -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()