forked from M-Labs/artiq
compiler: fix quoting of methods (fixes #423).
This commit is contained in:
parent
4c78bb4950
commit
4e5d752951
|
@ -105,12 +105,23 @@ 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):
|
||||||
|
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)
|
||||||
|
|
||||||
quote_loc = self._add('`')
|
quote_loc = self._add('`')
|
||||||
repr_loc = self._add(repr(value))
|
repr_loc = self._add(repr(value))
|
||||||
unquote_loc = self._add('`')
|
unquote_loc = self._add('`')
|
||||||
loc = quote_loc.join(unquote_loc)
|
loc = quote_loc.join(unquote_loc)
|
||||||
|
|
||||||
function_type = self.quote_function(value, self.expanded_from)
|
|
||||||
return asttyped.QuoteT(value=value, type=function_type, loc=loc)
|
return asttyped.QuoteT(value=value, type=function_type, loc=loc)
|
||||||
else:
|
else:
|
||||||
quote_loc = self._add('`')
|
quote_loc = self._add('`')
|
||||||
|
@ -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):
|
||||||
|
if isinstance(callee, pytypes.FunctionType):
|
||||||
signature = inspect.signature(callee)
|
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)
|
||||||
|
|
|
@ -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:])
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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()
|
Loading…
Reference in New Issue