forked from M-Labs/artiq
1
0
Fork 0

Implement methods.

This commit is contained in:
whitequark 2015-08-18 22:39:22 -07:00
parent 1040a409c3
commit 6c8de9b6d4
8 changed files with 158 additions and 40 deletions

View File

@ -205,4 +205,5 @@ def is_allocated(typ):
accum or not (is_none(typ) or is_bool(typ) or is_int(typ) or accum or not (is_none(typ) or is_bool(typ) or is_int(typ) or
is_float(typ) or is_range(typ) or is_float(typ) or is_range(typ) or
types.is_c_function(typ) or types.is_rpc_function(typ) or types.is_c_function(typ) or types.is_rpc_function(typ) or
types.is_method(typ) or
types.is_value(typ))) types.is_value(typ)))

View File

@ -711,13 +711,22 @@ class ARTIQIRGenerator(algorithm.Visitor):
finally: finally:
self.current_assign = old_assign self.current_assign = old_assign
if node.attr not in node.type.find().attributes: if node.attr not in obj.type.find().attributes:
# A class attribute. Get the constructor (class object) and # A class attribute. Get the constructor (class object) and
# extract the attribute from it. # extract the attribute from it.
constructor = obj.type.constructor print(node)
obj = self.append(ir.GetConstructor(self._env_for(constructor.name), print(obj)
constructor.name, constructor, constr_type = obj.type.constructor
name="constructor." + constructor.name)) constr = self.append(ir.GetConstructor(self._env_for(constr_type.name),
constr_type.name, constr_type,
name="constructor." + constr_type.name))
if types.is_function(constr.type.attributes[node.attr]):
# A method. Construct a method object instead.
func = self.append(ir.GetAttr(constr, node.attr))
return self.append(ir.Alloc([func, obj], node.type))
else:
obj = constr
if self.current_assign is None: if self.current_assign is None:
return self.append(ir.GetAttr(obj, node.attr, return self.append(ir.GetAttr(obj, node.attr,
@ -1413,36 +1422,49 @@ class ARTIQIRGenerator(algorithm.Visitor):
elif types.is_builtin(typ): elif types.is_builtin(typ):
return self.visit_builtin_call(node) return self.visit_builtin_call(node)
else: else:
if types.is_function(typ):
func = self.visit(node.func) func = self.visit(node.func)
args = [None] * (len(typ.args) + len(typ.optargs)) self_arg = None
fn_typ = typ
elif types.is_method(typ):
method = self.visit(node.func)
func = self.append(ir.GetAttr(method, "__func__"))
self_arg = self.append(ir.GetAttr(method, "__self__"))
fn_typ = types.get_method_function(typ)
args = [None] * (len(fn_typ.args) + len(fn_typ.optargs))
for index, arg_node in enumerate(node.args): for index, arg_node in enumerate(node.args):
arg = self.visit(arg_node) arg = self.visit(arg_node)
if index < len(typ.args): if index < len(fn_typ.args):
args[index] = arg args[index] = arg
else: else:
args[index] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) args[index] = self.append(ir.Alloc([arg], ir.TOption(arg.type)))
for keyword in node.keywords: for keyword in node.keywords:
arg = self.visit(keyword.value) arg = self.visit(keyword.value)
if keyword.arg in typ.args: if keyword.arg in fn_typ.args:
for index, arg_name in enumerate(typ.args): for index, arg_name in enumerate(fn_typ.args):
if keyword.arg == arg_name: if keyword.arg == arg_name:
assert args[index] is None assert args[index] is None
args[index] = arg args[index] = arg
break break
elif keyword.arg in typ.optargs: elif keyword.arg in fn_typ.optargs:
for index, optarg_name in enumerate(typ.optargs): for index, optarg_name in enumerate(fn_typ.optargs):
if keyword.arg == optarg_name: if keyword.arg == optarg_name:
assert args[len(typ.args) + index] is None assert args[len(fn_typ.args) + index] is None
args[len(typ.args) + index] = \ args[len(fn_typ.args) + index] = \
self.append(ir.Alloc([arg], ir.TOption(arg.type))) self.append(ir.Alloc([arg], ir.TOption(arg.type)))
break break
for index, optarg_name in enumerate(typ.optargs): for index, optarg_name in enumerate(fn_typ.optargs):
if args[len(typ.args) + index] is None: if args[len(fn_typ.args) + index] is None:
args[len(typ.args) + index] = \ args[len(fn_typ.args) + index] = \
self.append(ir.Alloc([], ir.TOption(typ.optargs[optarg_name]))) self.append(ir.Alloc([], ir.TOption(fn_typ.optargs[optarg_name])))
if self_arg is not None:
assert args[0] is None
args[0] = self_arg
assert None not in args assert None not in args

View File

@ -22,7 +22,7 @@ class Inferencer(algorithm.Visitor):
self.in_loop = False self.in_loop = False
self.has_return = False self.has_return = False
def _unify(self, typea, typeb, loca, locb, makenotes=None): def _unify(self, typea, typeb, loca, locb, makenotes=None, when=""):
try: try:
typea.unify(typeb) typea.unify(typeb)
except types.UnificationError as e: except types.UnificationError as e:
@ -45,16 +45,19 @@ class Inferencer(algorithm.Visitor):
locb)) locb))
highlights = [locb] if locb else [] highlights = [locb] if locb else []
if e.typea.find() == typea.find() and e.typeb.find() == typeb.find(): if e.typea.find() == typea.find() and e.typeb.find() == typeb.find() or \
e.typeb.find() == typea.find() and e.typea.find() == typeb.find():
diag = diagnostic.Diagnostic("error", diag = diagnostic.Diagnostic("error",
"cannot unify {typea} with {typeb}", "cannot unify {typea} with {typeb}{when}",
{"typea": printer.name(typea), "typeb": printer.name(typeb)}, {"typea": printer.name(typea), "typeb": printer.name(typeb),
"when": when},
loca, highlights, notes) loca, highlights, notes)
else: # give more detail else: # give more detail
diag = diagnostic.Diagnostic("error", diag = diagnostic.Diagnostic("error",
"cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", "cannot unify {typea} with {typeb}{when}: {fraga} is incompatible with {fragb}",
{"typea": printer.name(typea), "typeb": printer.name(typeb), {"typea": printer.name(typea), "typeb": printer.name(typeb),
"fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)}, "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb),
"when": when},
loca, highlights, notes) loca, highlights, notes)
self.engine.process(diag) self.engine.process(diag)
@ -88,13 +91,43 @@ class Inferencer(algorithm.Visitor):
object_type = node.value.type.find() object_type = node.value.type.find()
if not types.is_var(object_type): if not types.is_var(object_type):
if node.attr in object_type.attributes: if node.attr in object_type.attributes:
# assumes no free type variables in .attributes # Assumes no free type variables in .attributes.
self._unify(node.type, object_type.attributes[node.attr], self._unify(node.type, object_type.attributes[node.attr],
node.loc, None) node.loc, None)
elif types.is_instance(object_type) and \ elif types.is_instance(object_type) and \
node.attr in object_type.constructor.attributes: node.attr in object_type.constructor.attributes:
# assumes no free type variables in .attributes # Assumes no free type variables in .attributes.
self._unify(node.type, object_type.constructor.attributes[node.attr], attr_type = object_type.constructor.attributes[node.attr].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": node.attr, "type": types.TypePrinter().name(attr_type),
"class": object_type.name},
node.loc)
self.engine.process(diag)
return
else:
def makenotes(printer, typea, typeb, loca, locb):
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)},
locb)
]
self._unify(object_type, list(attr_type.args.values())[0],
node.value.loc, node.loc,
makenotes=makenotes,
when=" while inferring the type for self argument")
attr_type = types.TMethod(object_type, attr_type)
self._unify(node.type, attr_type,
node.loc, None) node.loc, None)
else: else:
diag = diagnostic.Diagnostic("error", diag = diagnostic.Diagnostic("error",
@ -695,7 +728,7 @@ class Inferencer(algorithm.Visitor):
return return
elif types.is_builtin(typ): elif types.is_builtin(typ):
return self.visit_builtin_call(node) return self.visit_builtin_call(node)
elif not types.is_function(typ): elif not (types.is_function(typ) or types.is_method(typ)):
diag = diagnostic.Diagnostic("error", diag = diagnostic.Diagnostic("error",
"cannot call this expression of type {type}", "cannot call this expression of type {type}",
{"type": types.TypePrinter().name(typ)}, {"type": types.TypePrinter().name(typ)},
@ -703,22 +736,34 @@ class Inferencer(algorithm.Visitor):
self.engine.process(diag) self.engine.process(diag)
return return
if types.is_function(typ):
typ_arity = typ.arity()
typ_args = typ.args
typ_optargs = typ.optargs
typ_ret = typ.ret
else:
typ = types.get_method_function(typ)
typ_arity = typ.arity() - 1
typ_args = OrderedDict(list(typ.args.items())[1:])
typ_optargs = typ.optargs
typ_ret = typ.ret
passed_args = dict() passed_args = dict()
if len(node.args) > typ.arity(): if len(node.args) > typ_arity:
note = diagnostic.Diagnostic("note", note = diagnostic.Diagnostic("note",
"extraneous argument(s)", {}, "extraneous argument(s)", {},
node.args[typ.arity()].loc.join(node.args[-1].loc)) node.args[typ_arity].loc.join(node.args[-1].loc))
diag = diagnostic.Diagnostic("error", diag = diagnostic.Diagnostic("error",
"this function of type {type} accepts at most {num} arguments", "this function of type {type} accepts at most {num} arguments",
{"type": types.TypePrinter().name(node.func.type), {"type": types.TypePrinter().name(node.func.type),
"num": typ.arity()}, "num": typ_arity},
node.func.loc, [], [note]) node.func.loc, [], [note])
self.engine.process(diag) self.engine.process(diag)
return return
for actualarg, (formalname, formaltyp) in \ for actualarg, (formalname, formaltyp) in \
zip(node.args, list(typ.args.items()) + list(typ.optargs.items())): zip(node.args, list(typ_args.items()) + list(typ_optargs.items())):
self._unify(actualarg.type, formaltyp, self._unify(actualarg.type, formaltyp,
actualarg.loc, None) actualarg.loc, None)
passed_args[formalname] = actualarg.loc passed_args[formalname] = actualarg.loc
@ -732,15 +777,15 @@ class Inferencer(algorithm.Visitor):
self.engine.process(diag) self.engine.process(diag)
return return
if keyword.arg in typ.args: if keyword.arg in typ_args:
self._unify(keyword.value.type, typ.args[keyword.arg], self._unify(keyword.value.type, typ_args[keyword.arg],
keyword.value.loc, None) keyword.value.loc, None)
elif keyword.arg in typ.optargs: elif keyword.arg in typ_optargs:
self._unify(keyword.value.type, typ.optargs[keyword.arg], self._unify(keyword.value.type, typ_optargs[keyword.arg],
keyword.value.loc, None) keyword.value.loc, None)
passed_args[keyword.arg] = keyword.arg_loc passed_args[keyword.arg] = keyword.arg_loc
for formalname in typ.args: for formalname in typ_args:
if formalname not in passed_args: if formalname not in passed_args:
note = diagnostic.Diagnostic("note", note = diagnostic.Diagnostic("note",
"the called function is of type {type}", "the called function is of type {type}",
@ -753,7 +798,7 @@ class Inferencer(algorithm.Visitor):
self.engine.process(diag) self.engine.process(diag)
return return
self._unify(node.type, typ.ret, self._unify(node.type, typ_ret,
node.loc, None) node.loc, None)
def visit_LambdaT(self, node): def visit_LambdaT(self, node):

View File

@ -192,6 +192,10 @@ class LLVMIRGenerator:
return llty return llty
else: else:
return ll.LiteralStructType([envarg, llty.as_pointer()]) return ll.LiteralStructType([envarg, llty.as_pointer()])
elif types.is_method(typ):
llfuncty = self.llty_of_type(types.get_method_function(typ))
llselfty = self.llty_of_type(types.get_method_self(typ))
return ll.LiteralStructType([llfuncty, llselfty])
elif builtins.is_none(typ): elif builtins.is_none(typ):
if for_return: if for_return:
return llvoid return llvoid

View File

@ -350,9 +350,20 @@ class TInstance(TMono):
self.attributes = attributes self.attributes = attributes
def __repr__(self): def __repr__(self):
return "py2llvm.types.TInstance({}, {]})".format( return "py2llvm.types.TInstance({}, {})".format(
repr(self.name), repr(self.attributes)) repr(self.name), repr(self.attributes))
class TMethod(TMono):
"""
A type of a method.
"""
def __init__(self, self_type, function_type):
super().__init__("method", {"self": self_type, "fn": function_type})
self.attributes = OrderedDict([
("__func__", function_type),
("__self__", self_type),
])
class TValue(Type): class TValue(Type):
""" """
@ -452,6 +463,17 @@ def is_instance(typ, name=None):
else: else:
return isinstance(typ, TInstance) return isinstance(typ, TInstance)
def is_method(typ):
return isinstance(typ.find(), TMethod)
def get_method_self(typ):
if is_method(typ):
return typ.find().params["self"]
def get_method_function(typ):
if is_method(typ):
return typ.find().params["fn"]
def is_value(typ): def is_value(typ):
return isinstance(typ.find(), TValue) return isinstance(typ.find(), TValue)

View File

@ -5,10 +5,15 @@ class c:
a = 1 a = 1
def f(): def f():
pass pass
def m(self):
pass
# CHECK-L: c:<constructor c {a: int(width='a), f: ()->NoneType}> # CHECK-L: c:<constructor c {a: int(width='a), f: ()->NoneType, m: (self:c)->NoneType}>
c c
# CHECK-L: .a:int(width='a) # CHECK-L: .a:int(width='a)
c.a c.a
# CHECK-L: .f:()->NoneType # CHECK-L: .f:()->NoneType
c.f c.f
# CHECK-L: .m:method(self=c, fn=(self:c)->NoneType)
c().m()

View File

@ -0,0 +1,16 @@
# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t
# RUN: OutputCheck %s --file-to-check=%t
class c:
def f():
pass
def g(self):
pass
# CHECK-L: ${LINE:+1}: error: function 'f()->NoneType' of class 'c' cannot accept a self argument
c().f()
c.g(1)
# CHECK-L: ${LINE:+1}: error: cannot unify c with int(width='a) while inferring the type for self argument
c().g()

View File

@ -5,6 +5,9 @@ class c:
a = 1 a = 1
def f(): def f():
return 2 return 2
def g(self):
return self.a + 5
assert c.a == 1 assert c.a == 1
assert c.f() == 2 assert c.f() == 2
assert c().g() == 6