Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/artiq
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 56d1a9bc57f4
Choose a base ref
...
head repository: m-labs/artiq
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 4b01e604dbd6
Choose a head ref
  • 7 commits
  • 3 files changed
  • 1 contributor

Commits on May 30, 2015

  1. Require nonlocal names to be bound in an outer scope.

    whitequark committed May 30, 2015
    Copy the full SHA
    f979a76 View commit details

Commits on Jun 4, 2015

  1. Implement inferencing for AugAssign.

    whitequark committed Jun 4, 2015
    Copy the full SHA
    76ce364 View commit details
  2. Add inferencing for Tuple, List, For.

    whitequark committed Jun 4, 2015
    Copy the full SHA
    995d84d View commit details
  3. Better error message for List inference.

    whitequark committed Jun 4, 2015
    3
    Copy the full SHA
    10a269d View commit details
  4. Error out on unsupported expressions by default.

    whitequark committed Jun 4, 2015
    Copy the full SHA
    c9623a1 View commit details
  5. Use a single type printer for inference errors.

    This way, type variable names will be consistent among all
    printed diagnostics.
    whitequark committed Jun 4, 2015
    Copy the full SHA
    1a08b50 View commit details
  6. Make unification reflective.

    whitequark committed Jun 4, 2015
    Copy the full SHA
    4b01e60 View commit details
Showing with 169 additions and 32 deletions.
  1. +43 −14 artiq/py2llvm/asttyped.py
  2. +19 −2 artiq/py2llvm/types.py
  3. +107 −16 artiq/py2llvm/typing.py
57 changes: 43 additions & 14 deletions artiq/py2llvm/asttyped.py
Original file line number Diff line number Diff line change
@@ -22,32 +22,61 @@ class scoped(object):
list of variables resolved as globals
"""

class ClassDefT(ast.ClassDef, scoped):
class argT(ast.arg, commontyped):
pass

class ClassDefT(ast.ClassDef, scoped):
pass
class FunctionDefT(ast.FunctionDef, scoped):
pass

class LambdaT(ast.Lambda, scoped):
class AttributeT(ast.Attribute, commontyped):
pass

class DictCompT(ast.DictComp, scoped):
class BinOpT(ast.BinOp, commontyped):
pass

class ListCompT(ast.ListComp, scoped):
class BoolOpT(ast.BoolOp, commontyped):
pass

class SetCompT(ast.SetComp, scoped):
class CallT(ast.Call, commontyped):
pass

class argT(ast.arg, commontyped):
class CompareT(ast.Compare, commontyped):
pass

class NumT(ast.Num, commontyped):
class DictT(ast.Dict, commontyped):
pass
class DictCompT(ast.DictComp, commontyped, scoped):
pass
class EllipsisT(ast.Ellipsis, commontyped):
pass
class GeneratorExpT(ast.GeneratorExp, commontyped, scoped):
pass
class IfExpT(ast.IfExp, commontyped):
pass
class LambdaT(ast.Lambda, commontyped, scoped):
pass
class ListT(ast.List, commontyped):
pass
class ListCompT(ast.ListComp, commontyped, scoped):
pass

class NameT(ast.Name, commontyped):
pass

class NameConstantT(ast.NameConstant, commontyped):
pass
class NumT(ast.Num, commontyped):
pass
class SetT(ast.Set, commontyped):
pass
class SetCompT(ast.SetComp, commontyped, scoped):
pass
class StrT(ast.Str, commontyped):
pass
class StarredT(ast.Starred, commontyped):
pass
class SubscriptT(ast.Subscript, commontyped):
pass
class TupleT(ast.Tuple, commontyped):
pass
class UnaryOpT(ast.UnaryOp, commontyped):
pass
class YieldT(ast.Yield, commontyped):
pass
class YieldFromT(ast.YieldFrom, commontyped):
pass
21 changes: 19 additions & 2 deletions artiq/py2llvm/types.py
Original file line number Diff line number Diff line change
@@ -80,12 +80,17 @@ def unify(self, other):
assert self.params.keys() == other.params.keys()
for param in self.params:
self.params[param].unify(other.params[param])
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)

def __repr__(self):
return "TMono(%s, %s)" % (repr(self.name), repr(self.params))

def __getitem__(self, param):
return self.params[param]

def __eq__(self, other):
return isinstance(other, TMono) and \
self.name == other.name and \
@@ -107,6 +112,8 @@ def unify(self, other):
if isinstance(other, TTuple) and len(self.elts) == len(other.elts):
for selfelt, otherelt in zip(self.elts, other.elts):
selfelt.unify(otherelt)
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)

@@ -133,7 +140,9 @@ def find(self):
return self

def unify(self, other):
if self != other:
if isinstance(other, TVar):
other.unify(self)
elif self != other:
raise UnificationError(self, other)

def __repr__(self):
@@ -150,14 +159,22 @@ def TBool():
"""A boolean type."""
return TMono("bool")

def TInt(width=TVar()):
def TInt(width=None):
"""A generic integer type."""
if width is None:
width = TVar()
return TMono("int", {"width": width})

def TFloat():
"""A double-precision floating point type."""
return TMono("float")

def TList(elt=None):
"""A generic list type."""
if elt is None:
elt = TVar()
return TMono("list", {"elt": elt})


class TypePrinter(object):
"""
123 changes: 107 additions & 16 deletions artiq/py2llvm/typing.py
Original file line number Diff line number Diff line change
@@ -4,8 +4,9 @@
# This visitor will be called for every node with a scope,
# i.e.: class, function, comprehension, lambda
class LocalExtractor(algorithm.Visitor):
def __init__(self, engine):
def __init__(self, env_stack, engine):
super().__init__()
self.env_stack = env_stack
self.engine = engine

self.in_root = False
@@ -103,6 +104,19 @@ def visit_Nonlocal(self, node):
for name, loc in zip(node.names, node.name_locs):
self._check_not_in(name, self.global_, 'global', 'nonlocal', loc)
self._check_not_in(name, self.params, 'a parameter', 'nonlocal', loc)

found = False
for outer_env in reversed(self.env_stack):
if name in outer_env:
found = True
break
if not found:
diag = diagnostic.Diagnostic('fatal',
"can't declare name '{name}' as nonlocal: it is not bound in any outer scope",
{"name": name},
loc, [node.keyword_loc])
self.engine.process(diag)

self.nonlocal_.add(name)

def visit_ExceptHandler(self, node):
@@ -117,36 +131,43 @@ def __init__(self, engine):
self.engine = engine
self.env_stack = [{}]

def _unify(self, typea, typeb, loca, locb):
def _unify(self, typea, typeb, loca, locb, kind):
try:
typea.unify(typeb)
except types.UnificationError as e:
note1 = diagnostic.Diagnostic('note',
"expression of type {typea}",
{"typea": types.TypePrinter().name(typea)},
loca)
printer = types.TypePrinter()

if kind == 'generic':
note1 = diagnostic.Diagnostic('note',
"expression of type {typea}",
{"typea": printer.name(typea)},
loca)
elif kind == 'expects':
note1 = diagnostic.Diagnostic('note',
"expression expecting an operand of type {typea}",
{"typea": printer.name(typea)},
loca)

note2 = diagnostic.Diagnostic('note',
"expression of type {typeb}",
{"typeb": types.TypePrinter().name(typeb)},
{"typeb": printer.name(typeb)},
locb)

if e.typea.find() == typea.find() and e.typeb.find() == typeb.find():
diag = diagnostic.Diagnostic('fatal',
"cannot unify {typea} with {typeb}",
{"typea": types.TypePrinter().name(typea),
"typeb": types.TypePrinter().name(typeb)},
{"typea": printer.name(typea), "typeb": printer.name(typeb)},
loca, [locb], notes=[note1, note2])
else: # give more detail
diag = diagnostic.Diagnostic('fatal',
"cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}",
{"typea": types.TypePrinter().name(typea),
"typeb": types.TypePrinter().name(typeb),
"fraga": types.TypePrinter().name(e.typea),
"fragb": types.TypePrinter().name(e.typeb),},
{"typea": printer.name(typea), "typeb": printer.name(typeb),
"fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)},
loca, [locb], notes=[note1, note2])
self.engine.process(diag)

def visit_FunctionDef(self, node):
extractor = LocalExtractor(engine=self.engine)
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)

self.env_stack.append(extractor.typing_env)
@@ -170,6 +191,8 @@ def _find_name(self, name, loc):
"name '{name}' is not bound to anything", {"name":name}, loc)
self.engine.process(diag)

# Visitors that replace node with a typed node
#
def visit_arg(self, node):
return asttyped.argT(type=self._find_name(node.arg, node.loc),
arg=node.arg, annotation=self.visit(node.annotation),
@@ -182,7 +205,7 @@ def visit_Num(self, node):
typ = types.TFloat()
else:
diag = diagnostic.Diagnostic('fatal',
"numeric type {type} is not supported", node.n.__class__.__name__,
"numeric type {type} is not supported", {"type": node.n.__class__.__name__},
node.loc)
self.engine.process(diag)
return asttyped.NumT(type=typ,
@@ -192,6 +215,32 @@ def visit_Name(self, node):
return asttyped.NameT(type=self._find_name(node.id, node.loc),
id=node.id, ctx=node.ctx, loc=node.loc)

def visit_Tuple(self, node):
node = self.generic_visit(node)
return asttyped.TupleT(type=types.TTuple([x.type for x in node.elts]),
elts=node.elts, ctx=node.ctx, loc=node.loc)

def visit_List(self, node):
node = self.generic_visit(node)
node = asttyped.ListT(type=types.TList(),
elts=node.elts, ctx=node.ctx, loc=node.loc)
for elt in node.elts:
self._unify(node.type['elt'], elt.type,
node.loc, elt.loc, kind='expects')
return node

def visit_Subscript(self, node):
node = self.generic_visit(node)
node = asttyped.SubscriptT(type=types.TVar(),
value=node.value, slice=node.slice, ctx=node.ctx,
loc=node.loc)
# TODO: support more than just lists
self._unify(types.TList(node.type), node.value.type,
node.loc, node.value.loc, kind='expects')
return node

# Visitors that just unify types
#
def visit_Assign(self, node):
node = self.generic_visit(node)
if len(node.targets) > 1:
@@ -202,6 +251,47 @@ def visit_Assign(self, node):
node.targets[0].loc, node.value.loc)
return node

def visit_AugAssign(self, node):
node = self.generic_visit(node)
self._unify(node.target.type, node.value.type,
node.target.loc, node.value.loc)
return node

def visit_For(self, node):
node = self.generic_visit(node)
# TODO: support more than just lists
self._unify(TList(node.target.type), node.iter.type,
node.target.loc, node.iter.loc)
return node

# Unsupported visitors
#
def visit_unsupported(self, node):
diag = diagnostic.Diagnostic('fatal',
"this syntax is not supported", {},
node.loc)
self.engine.process(diag)

visit_Attribute = visit_unsupported
visit_BinOp = visit_unsupported
visit_BoolOp = visit_unsupported
visit_Call = visit_unsupported
visit_Compare = visit_unsupported
visit_Dict = visit_unsupported
visit_DictComp = visit_unsupported
visit_Ellipsis = visit_unsupported
visit_GeneratorExp = visit_unsupported
visit_IfExp = visit_unsupported
visit_Lambda = visit_unsupported
visit_ListComp = visit_unsupported
visit_Set = visit_unsupported
visit_SetComp = visit_unsupported
visit_Str = visit_unsupported
visit_Starred = visit_unsupported
visit_UnaryOp = visit_unsupported
visit_Yield = visit_unsupported
visit_YieldFrom = visit_unsupported

class Printer(algorithm.Visitor):
def __init__(self, buf):
self.rewriter = source.Rewriter(buf)
@@ -212,7 +302,8 @@ def rewrite(self):

def generic_visit(self, node):
if hasattr(node, 'type'):
self.rewriter.insert_after(node.loc, " : %s" % self.type_printer.name(node.type))
self.rewriter.insert_after(node.loc,
":%s" % self.type_printer.name(node.type))

super().generic_visit(node)