Implement class definitions and class attribute access.

This commit is contained in:
whitequark 2015-08-15 09:45:16 -04:00
parent fd3c8a2830
commit 00efc8c636
16 changed files with 235 additions and 88 deletions

View File

@ -25,15 +25,15 @@ class scoped(object):
class argT(ast.arg, commontyped):
pass
class ClassDefT(ast.ClassDef, scoped):
pass
class ClassDefT(ast.ClassDef):
_types = ("constructor_type",)
class FunctionDefT(ast.FunctionDef, scoped):
_types = ("signature_type",)
class ModuleT(ast.Module, scoped):
pass
class ExceptHandlerT(ast.ExceptHandler):
_fields = ("filter", "name", "body") # rename ast.ExceptHandler.type
_fields = ("filter", "name", "body") # rename ast.ExceptHandler.type to filter
_types = ("name_type",)
class SliceT(ast.Slice, commontyped):

View File

@ -96,44 +96,32 @@ class TException(types.TMono):
def __init__(self, name="Exception"):
super().__init__(name)
class TIndexError(TException):
def __init__(self):
super().__init__("IndexError")
class TValueError(TException):
def __init__(self):
super().__init__("ValueError")
class TZeroDivisionError(TException):
def __init__(self):
super().__init__("ZeroDivisionError")
def fn_bool():
return types.TConstructor("bool")
return types.TConstructor(TBool())
def fn_int():
return types.TConstructor("int")
return types.TConstructor(TInt())
def fn_float():
return types.TConstructor("float")
return types.TConstructor(TFloat())
def fn_str():
return types.TConstructor("str")
return types.TConstructor(TStr())
def fn_list():
return types.TConstructor("list")
return types.TConstructor(TList())
def fn_Exception():
return types.TExceptionConstructor("Exception")
return types.TExceptionConstructor(TException("Exception"))
def fn_IndexError():
return types.TExceptionConstructor("IndexError")
return types.TExceptionConstructor(TException("IndexError"))
def fn_ValueError():
return types.TExceptionConstructor("ValueError")
return types.TExceptionConstructor(TException("ValueError"))
def fn_ZeroDivisionError():
return types.TExceptionConstructor("ZeroDivisionError")
return types.TExceptionConstructor(TException("ZeroDivisionError"))
def fn_range():
return types.TBuiltinFunction("range")
@ -215,4 +203,4 @@ def is_collection(typ):
def is_allocated(typ):
return typ.fold(False, lambda accum, typ:
is_list(typ) or is_str(typ) or types.is_function(typ) or
is_exception(typ))
is_exception(typ) or types.is_constructor(typ))

View File

@ -636,9 +636,9 @@ class SetAttr(Instruction):
assert isinstance(attr, (str, int))
assert isinstance(value, Value)
if isinstance(attr, int):
assert value.type == obj.type.elts[attr]
assert value.type == obj.type.elts[attr].find()
else:
assert value.type == obj.type.attributes[attr]
assert value.type == obj.type.attributes[attr].find()
super().__init__([obj, value], builtins.TNone(), name)
self.attr = attr

View File

@ -73,6 +73,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.name = [module_name] if module_name != "" else []
self.current_loc = None
self.current_function = None
self.current_class = None
self.current_globals = set()
self.current_block = None
self.current_env = None
@ -156,6 +157,17 @@ class ARTIQIRGenerator(algorithm.Visitor):
# Statement visitors
def visit_ClassDefT(self, node):
klass = self.append(ir.Alloc([], node.constructor_type,
name="class.{}".format(node.name)))
self._set_local(node.name, klass)
try:
old_class, self.current_class = self.current_class, klass
self.visit(node.body)
finally:
self.current_class = old_class
def visit_function(self, node, is_lambda, is_internal):
if is_lambda:
name = "lambda@{}:{}".format(node.loc.line(), node.loc.column())
@ -239,9 +251,12 @@ class ARTIQIRGenerator(algorithm.Visitor):
return self.append(ir.Closure(func, self.current_env))
def visit_FunctionDefT(self, node):
def visit_FunctionDefT(self, node, in_class=None):
func = self.visit_function(node, is_lambda=False, is_internal=len(self.name) > 2)
self._set_local(node.name, func)
if in_class is None:
self._set_local(node.name, func)
else:
self.append(ir.SetAttr(in_class, node.name, func))
def visit_Return(self, node):
if node.value is None:
@ -668,9 +683,19 @@ class ARTIQIRGenerator(algorithm.Visitor):
return self.current_env
def _get_local(self, name):
return self.append(ir.GetLocal(self._env_for(name), name, name="local." + name))
if self.current_class is not None and \
name in self.current_class.type.attributes:
return self.append(ir.GetAttr(self.current_class, name,
name="local." + name))
return self.append(ir.GetLocal(self._env_for(name), name,
name="local." + name))
def _set_local(self, name, value):
if self.current_class is not None and \
name in self.current_class.type.attributes:
return self.append(ir.SetAttr(self.current_class, name, value))
self.append(ir.SetLocal(self._env_for(name), name, value))
def visit_NameT(self, node):
@ -706,7 +731,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
head = self.current_block
self.current_block = out_of_bounds_block = self.add_block()
exn = self.alloc_exn(builtins.TIndexError(),
exn = self.alloc_exn(builtins.TException("IndexError"),
ir.Constant("index {0} out of bounds 0:{1}", builtins.TStr()),
index, length)
self.raise_exn(exn, loc=loc)
@ -790,7 +815,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
step = self.visit(node.slice.step)
self._make_check(
self.append(ir.Compare(ast.NotEq(loc=None), step, ir.Constant(0, step.type))),
lambda: self.alloc_exn(builtins.TValueError(),
lambda: self.alloc_exn(builtins.TException("ValueError"),
ir.Constant("step cannot be zero", builtins.TStr())),
loc=node.slice.step.loc)
else:
@ -811,7 +836,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
name="slice.size"))
self._make_check(
self.append(ir.Compare(ast.LtE(loc=None), slice_size, length)),
lambda: self.alloc_exn(builtins.TValueError(),
lambda: self.alloc_exn(builtins.TException("ValueError"),
ir.Constant("slice size {0} is larger than iterable length {1}",
builtins.TStr()),
slice_size, length),
@ -894,7 +919,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
self._make_check(
self.append(ir.Compare(ast.Eq(loc=None), length,
ir.Constant(len(node.elts), self._size_type))),
lambda: self.alloc_exn(builtins.TValueError(),
lambda: self.alloc_exn(builtins.TException("ValueError"),
ir.Constant("list must be {0} elements long to decompose", builtins.TStr()),
length))
@ -1002,13 +1027,13 @@ class ARTIQIRGenerator(algorithm.Visitor):
# Check for negative shift amount.
self._make_check(
self.append(ir.Compare(ast.GtE(loc=None), rhs, ir.Constant(0, rhs.type))),
lambda: self.alloc_exn(builtins.TValueError(),
lambda: self.alloc_exn(builtins.TException("ValueError"),
ir.Constant("shift amount must be nonnegative", builtins.TStr())),
loc=node.right.loc)
elif isinstance(node.op, (ast.Div, ast.FloorDiv)):
self._make_check(
self.append(ir.Compare(ast.NotEq(loc=None), rhs, ir.Constant(0, rhs.type))),
lambda: self.alloc_exn(builtins.TZeroDivisionError(),
lambda: self.alloc_exn(builtins.TException("ZeroDivisionError"),
ir.Constant("cannot divide by zero", builtins.TStr())),
loc=node.right.loc)

View File

@ -3,7 +3,8 @@
to a typedtree (:mod:`..asttyped`).
"""
from pythonparser import algorithm, diagnostic
from collections import OrderedDict
from pythonparser import ast, algorithm, diagnostic
from .. import asttyped, types, builtins
# This visitor will be called for every node with a scope,
@ -16,7 +17,7 @@ class LocalExtractor(algorithm.Visitor):
self.in_root = False
self.in_assign = False
self.typing_env = {}
self.typing_env = OrderedDict()
# which names are global have to be recorded in the current scope
self.global_ = set()
@ -189,6 +190,7 @@ class ASTTypedRewriter(algorithm.Transformer):
self.engine = engine
self.globals = None
self.env_stack = [prelude]
self.in_class = None
def _try_find_name(self, name):
for typing_env in reversed(self.env_stack):
@ -196,11 +198,17 @@ class ASTTypedRewriter(algorithm.Transformer):
return typing_env[name]
def _find_name(self, name, loc):
if self.in_class is not None:
typ = self.in_class.constructor_type.attributes.get(name)
if typ is not None:
return typ
typ = self._try_find_name(name)
if typ is not None:
return typ
diag = diagnostic.Diagnostic("fatal",
"name '{name}' is not bound to anything", {"name":name}, loc)
"undefined variable '{name}'", {"name":name}, loc)
self.engine.process(diag)
# Visitors that replace node with a typed node
@ -239,6 +247,66 @@ class ASTTypedRewriter(algorithm.Transformer):
finally:
self.env_stack.pop()
def visit_ClassDef(self, node):
if any(node.bases) or any(node.keywords) or \
node.starargs is not None or node.kwargs is not None:
diag = diagnostic.Diagnostic("error",
"inheritance is not supported", {},
node.lparen_loc.join(node.rparen_loc))
self.engine.process(diag)
for child in node.body:
if isinstance(child, (ast.Assign, ast.FunctionDef, ast.Pass)):
continue
diag = diagnostic.Diagnostic("fatal",
"class body must contain only assignments and function definitions", {},
child.loc)
self.engine.process(diag)
if node.name in self.env_stack[-1]:
diag = diagnostic.Diagnostic("fatal",
"variable '{name}' is already defined", {"name":name}, loc)
self.engine.process(diag)
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
# Now we create two types.
# The first type is the type of instances created by the constructor.
# Its attributes are those of the class environment, but wrapped
# appropriately so that they are linked to the class from which they
# originate.
instance_type = types.TMono(node.name)
instance_type.attributes = OrderedDict({}) # TODO
# The second type is the type of the constructor itself (in other words,
# the class object): it is simply a singleton type that has the class
# environment as attributes.
constructor_type = types.TConstructor(instance_type)
constructor_type.attributes = extractor.typing_env
self.env_stack[-1][node.name] = constructor_type
node = asttyped.ClassDefT(
constructor_type=constructor_type,
name=node.name,
bases=self.visit(node.bases), keywords=self.visit(node.keywords),
starargs=self.visit(node.starargs), kwargs=self.visit(node.kwargs),
body=node.body,
decorator_list=self.visit(node.decorator_list),
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
lparen_loc=node.lparen_loc, star_loc=node.star_loc,
dstar_loc=node.dstar_loc, rparen_loc=node.rparen_loc,
colon_loc=node.colon_loc, at_locs=node.at_locs,
loc=node.loc)
try:
old_in_class, self.in_class = self.in_class, node
return self.generic_visit(node)
finally:
self.in_class = old_in_class
def visit_arg(self, node):
return asttyped.argT(type=self._find_name(node.arg, node.loc),
arg=node.arg, annotation=self.visit(node.annotation),
@ -426,7 +494,6 @@ class ASTTypedRewriter(algorithm.Transformer):
visit_YieldFrom = visit_unsupported
# stmt
visit_ClassDef = visit_unsupported
visit_Delete = visit_unsupported
visit_Import = visit_unsupported
visit_ImportFrom = visit_unsupported

View File

@ -467,7 +467,7 @@ class Inferencer(algorithm.Visitor):
else:
diagnose(valid_forms())
self._unify(node.type, getattr(builtins, "T" + typ.name)(),
self._unify(node.type, typ.instance,
node.loc, None)
elif types.is_builtin(typ, "bool"):
valid_forms = lambda: [
@ -895,38 +895,47 @@ class Inferencer(algorithm.Visitor):
"decorators are not supported", {},
node.at_locs[index], [decorator.loc])
self.engine.process(diag)
return
old_function, self.function = self.function, node
old_in_loop, self.in_loop = self.in_loop, False
old_has_return, self.has_return = self.has_return, False
try:
old_function, self.function = self.function, node
old_in_loop, self.in_loop = self.in_loop, False
old_has_return, self.has_return = self.has_return, False
self.generic_visit(node)
self.generic_visit(node)
# Lack of return statements is not the only case where the return
# type cannot be inferred. The other one is infinite (possibly mutual)
# recursion. Since Python functions don't have to return a value,
# we ignore that one.
if not self.has_return:
def makenotes(printer, typea, typeb, loca, locb):
return [
diagnostic.Diagnostic("note",
"function with return type {typea}",
{"typea": printer.name(typea)},
node.name_loc),
]
self._unify(node.return_type, builtins.TNone(),
node.name_loc, None, makenotes)
self.function = old_function
self.in_loop = old_in_loop
self.has_return = old_has_return
# Lack of return statements is not the only case where the return
# type cannot be inferred. The other one is infinite (possibly mutual)
# recursion. Since Python functions don't have to return a value,
# we ignore that one.
if not self.has_return:
def makenotes(printer, typea, typeb, loca, locb):
return [
diagnostic.Diagnostic("note",
"function with return type {typea}",
{"typea": printer.name(typea)},
node.name_loc),
]
self._unify(node.return_type, builtins.TNone(),
node.name_loc, None, makenotes)
finally:
self.function = old_function
self.in_loop = old_in_loop
self.has_return = old_has_return
signature_type = self._type_from_arguments(node.args, node.return_type)
if signature_type:
self._unify(node.signature_type, signature_type,
node.name_loc, None)
def visit_ClassDefT(self, node):
if any(node.decorator_list):
diag = diagnostic.Diagnostic("error",
"decorators are not supported", {},
node.at_locs[0], [node.decorator_list[0].loc])
self.engine.process(diag)
self.generic_visit(node)
def visit_Return(self, node):
if not self.function:
diag = diagnostic.Diagnostic("error",

View File

@ -437,7 +437,7 @@ class LLVMIRGenerator:
size=llsize)
llvalue = self.llbuilder.insert_value(llvalue, llalloc, 1, name=insn.name)
return llvalue
elif builtins.is_exception(insn.type):
elif builtins.is_exception(insn.type) or types.is_constructor(insn.type):
llalloc = self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True))
for index, operand in enumerate(insn.operands):
lloperand = self.map(operand)

View File

@ -319,9 +319,17 @@ class TConstructor(TBuiltin):
A type of a constructor of a builtin class, e.g. ``list``.
Note that this is not the same as the type of an instance of
the class, which is ``TMono("list", ...)``.
:ivar instance: (:class:`Type`)
the type of the instance created by this constructor
"""
class TExceptionConstructor(TBuiltin):
def __init__(self, instance):
assert isinstance(instance, TMono)
super().__init__(instance.name)
self.instance = instance
class TExceptionConstructor(TConstructor):
"""
A type of a constructor of a builtin exception, e.g. ``Exception``.
Note that this is not the same as the type of an instance of
@ -402,6 +410,14 @@ def is_builtin(typ, name=None):
return isinstance(typ, TBuiltin) and \
typ.name == name
def is_constructor(typ, name=None):
typ = typ.find()
if name is not None:
return isinstance(typ, TConstructor) and \
typ.name == name
else:
return isinstance(typ, TConstructor)
def is_exn_constructor(typ, name=None):
typ = typ.find()
if name is not None:
@ -459,9 +475,11 @@ class TypePrinter(object):
elif isinstance(typ, TFunction):
return signature
elif isinstance(typ, TBuiltinFunction):
return "<function %s>" % typ.name
return "<function {}>".format(typ.name)
elif isinstance(typ, (TConstructor, TExceptionConstructor)):
return "<constructor %s>" % typ.name
attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr]))
for attr in typ.attributes])
return "<constructor {} {{{}}}>".format(typ.name, attrs)
elif isinstance(typ, TValue):
return repr(typ.value)
else:

View File

@ -208,7 +208,7 @@ class EscapeValidator(algorithm.Visitor):
loc)
]
def visit_in_region(self, node, region):
def visit_in_region(self, node, region, typing_env):
try:
old_youngest_region = self.youngest_region
self.youngest_region = region
@ -216,8 +216,8 @@ class EscapeValidator(algorithm.Visitor):
old_youngest_env = self.youngest_env
self.youngest_env = {}
for name in node.typing_env:
if builtins.is_allocated(node.typing_env[name]):
for name in typing_env:
if builtins.is_allocated(typing_env[name]):
self.youngest_env[name] = Region(None) # not yet known
else:
self.youngest_env[name] = None # lives forever
@ -230,11 +230,15 @@ class EscapeValidator(algorithm.Visitor):
self.youngest_region = old_youngest_region
def visit_ModuleT(self, node):
self.visit_in_region(node, None)
self.visit_in_region(node, None, node.typing_env)
def visit_FunctionDefT(self, node):
self.youngest_env[node.name] = self.youngest_region
self.visit_in_region(node, Region(node.loc))
self.visit_in_region(node, Region(node.loc), node.typing_env)
def visit_ClassDefT(self, node):
self.youngest_env[node.name] = self.youngest_region
self.visit_in_region(node, Region(node.loc), node.constructor_type.attributes)
# Only three ways for a pointer to escape:
# * Assigning or op-assigning it (we ensure an outlives relationship)

View File

@ -1,28 +1,28 @@
# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# CHECK-L: bool:<constructor bool>():bool
# CHECK-L: bool:<constructor bool {}>():bool
bool()
# CHECK-L: bool:<constructor bool>([]:list(elt='a)):bool
# CHECK-L: bool:<constructor bool {}>([]:list(elt='a)):bool
bool([])
# CHECK-L: int:<constructor int>():int(width='b)
# CHECK-L: int:<constructor int {}>():int(width='b)
int()
# CHECK-L: int:<constructor int>(1.0:float):int(width='c)
# CHECK-L: int:<constructor int {}>(1.0:float):int(width='c)
int(1.0)
# CHECK-L: int:<constructor int>(1.0:float, width=64:int(width='d)):int(width=64)
# CHECK-L: int:<constructor int {}>(1.0:float, width=64:int(width='d)):int(width=64)
int(1.0, width=64)
# CHECK-L: float:<constructor float>():float
# CHECK-L: float:<constructor float {}>():float
float()
# CHECK-L: float:<constructor float>(1:int(width='e)):float
# CHECK-L: float:<constructor float {}>(1:int(width='e)):float
float(1)
# CHECK-L: list:<constructor list>():list(elt='f)
# CHECK-L: list:<constructor list {}>():list(elt='f)
list()
# CHECK-L: len:<function len>([]:list(elt='g)):int(width=32)

View File

@ -0,0 +1,14 @@
# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t
# RUN: OutputCheck %s --file-to-check=%t
class c:
a = 1
def f():
pass
# CHECK-L: c:<constructor c {a: int(width='a), f: ()->NoneType}>
c
# CHECK-L: .a:int(width='a)
c.a
# CHECK-L: .f:()->NoneType
c.f

View File

@ -0,0 +1,10 @@
# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# CHECK-L: ${LINE:+1}: error: inheritance is not supported
class a(1):
pass
class b:
# CHECK-L: ${LINE:+1}: fatal: class body must contain only assignments and function definitions
x += 1

View File

@ -1,5 +1,5 @@
# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# CHECK-L: ${LINE:+1}: fatal: name 'x' is not bound to anything
# CHECK-L: ${LINE:+1}: fatal: undefined variable 'x'
x

View File

@ -1,7 +1,7 @@
# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# CHECK-L: Exception:<constructor Exception>
# CHECK-L: Exception:<constructor Exception {}>
Exception
try:

View File

@ -58,16 +58,16 @@ k = "x"
# CHECK-L: k:str
IndexError()
# CHECK-L: IndexError:<constructor IndexError>():IndexError
# CHECK-L: IndexError:<constructor IndexError {}>():IndexError
IndexError("x")
# CHECK-L: IndexError:<constructor IndexError>("x":str):IndexError
# CHECK-L: IndexError:<constructor IndexError {}>("x":str):IndexError
IndexError("x", 1)
# CHECK-L: IndexError:<constructor IndexError>("x":str, 1:int(width=64)):IndexError
# CHECK-L: IndexError:<constructor IndexError {}>("x":str, 1:int(width=64)):IndexError
IndexError("x", 1, 1)
# CHECK-L: IndexError:<constructor IndexError>("x":str, 1:int(width=64), 1:int(width=64)):IndexError
# CHECK-L: IndexError:<constructor IndexError {}>("x":str, 1:int(width=64), 1:int(width=64)):IndexError
IndexError("x", 1, 1, 1)
# CHECK-L: IndexError:<constructor IndexError>("x":str, 1:int(width=64), 1:int(width=64), 1:int(width=64)):IndexError
# CHECK-L: IndexError:<constructor IndexError {}>("x":str, 1:int(width=64), 1:int(width=64), 1:int(width=64)):IndexError

View File

@ -0,0 +1,12 @@
# RUN: %python -m artiq.compiler.testbench.jit %s
# RUN: %python %s
class c:
a = 1
def f():
return 2
# CHECK-L: a 1
print("a", c.a)
# CHECK-L: f() 2
print("f()", c.f())