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: 56bba3009d57
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: 4b4805265d14
Choose a head ref
  • 4 commits
  • 4 files changed
  • 1 contributor

Commits on Jun 13, 2015

  1. Split off builtins from types.

    builtins will contain attribute definitions as well.
    whitequark committed Jun 13, 2015
    Copy the full SHA
    61434a8 View commit details
  2. Split ASTTypedRewriter off Inferencer.

    whitequark committed Jun 13, 2015
    Copy the full SHA
    4c95647 View commit details
  3. Error out on unsupported statements.

    whitequark committed Jun 13, 2015
    Copy the full SHA
    5555171 View commit details
  4. Add support for Break and Continue.

    whitequark committed Jun 13, 2015
    Copy the full SHA
    4b48052 View commit details
Showing with 244 additions and 182 deletions.
  1. +63 −0 artiq/py2llvm/builtins.py
  2. +2 −56 artiq/py2llvm/types.py
  3. +160 −126 artiq/py2llvm/typing.py
  4. +19 −0 lit-test/py2llvm/typing/error_control_flow.py
63 changes: 63 additions & 0 deletions artiq/py2llvm/builtins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
The :mod:`builtins` module contains the builtin Python and ARTIQ
types, such as int or float.
"""

from . import types

class TNone(types.TMono):
def __init__(self):
super().__init__("NoneType")

class TBool(types.TMono):
def __init__(self):
super().__init__("bool")

class TInt(types.TMono):
def __init__(self, width=None):
if width is None:
width = types.TVar()
super().__init__("int", {"width": width})

class TFloat(types.TMono):
def __init__(self):
super().__init__("float")

class TTuple(types.Type):
"""A tuple type."""

def __init__(self, elts=[]):
self.elts = elts

def find(self):
return self

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)

def __repr__(self):
return "TTuple(%s)" % (", ".join(map(repr, self.elts)))

def __eq__(self, other):
return isinstance(other, TTuple) and \
self.elts == other.elts

def __ne__(self, other):
return not (self == other)

class TList(types.TMono):
def __init__(self, elt=None):
if elt is None:
elt = types.TVar()
super().__init__("list", {"elt": elt})


def is_numeric(typ):
return isinstance(typ, types.TMono) and \
typ.name in ('int', 'float')
58 changes: 2 additions & 56 deletions artiq/py2llvm/types.py
Original file line number Diff line number Diff line change
@@ -37,6 +37,8 @@ class TVar(Type):
folded into this class.
"""

attributes = ()

def __init__(self):
self.parent = self

@@ -99,34 +101,6 @@ def __eq__(self, other):
def __ne__(self, other):
return not (self == other)

class TTuple(Type):
"""A tuple type."""

def __init__(self, elts=[]):
self.elts = elts

def find(self):
return self

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)

def __repr__(self):
return "TTuple(%s)" % (", ".join(map(repr, self.elts)))

def __eq__(self, other):
return isinstance(other, TTuple) and \
self.elts == other.elts

def __ne__(self, other):
return not (self == other)

class TValue(Type):
"""
A type-level value (such as the integer denoting width of
@@ -155,30 +129,6 @@ def __eq__(self, other):
def __ne__(self, other):
return not (self == other)

def TNone():
"""The type of None."""
return TMono("NoneType")

def TBool():
"""A boolean type."""
return TMono("bool")

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})


def is_var(typ):
return isinstance(typ, TVar)
@@ -190,10 +140,6 @@ def is_mono(typ, name, **params):
return isinstance(typ, TMono) and \
typ.name == name and params_match

def is_numeric(typ):
return isinstance(typ, TMono) and \
typ.name in ('int', 'float')


class TypePrinter(object):
"""
286 changes: 160 additions & 126 deletions artiq/py2llvm/typing.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pythonparser import source, ast, algorithm, diagnostic, parse_buffer
from . import asttyped, types
from . import asttyped, types, builtins

# This visitor will be called for every node with a scope,
# i.e.: class, function, comprehension, lambda
@@ -126,61 +126,10 @@ def visit_ExceptHandler(self, node):
self.visit(stmt)


class Inferencer(algorithm.Transformer):
class ASTTypedRewriter(algorithm.Transformer):
def __init__(self, engine):
self.engine = engine
self.env_stack = []
self.function = None # currently visited function

def _unify(self, typea, typeb, loca, locb, makenotes=None):
try:
typea.unify(typeb)
except types.UnificationError as e:
printer = types.TypePrinter()

if makenotes:
notes = makenotes(printer, typea, typeb, loca, locb)
else:
notes = [
diagnostic.Diagnostic("note",
"expression of type {typea}",
{"typea": printer.name(typea)},
loca),
diagnostic.Diagnostic("note",
"expression of type {typeb}",
{"typeb": printer.name(typeb)},
locb)
]

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

# makenotes for the case where types of multiple elements are unified
# with the type of parent expression
def _makenotes_elts(self, elts, kind):
def makenotes(printer, typea, typeb, loca, locb):
return [
diagnostic.Diagnostic("note",
"{kind} of type {typea}",
{"kind": kind, "typea": printer.name(elts[0].type)},
elts[0].loc),
diagnostic.Diagnostic("note",
"{kind} of type {typeb}",
{"kind": kind, "typeb": printer.name(typeb)},
locb)
]
return makenotes

def _find_name(self, name, loc):
for typing_env in reversed(self.env_stack):
@@ -199,7 +148,12 @@ def visit_Module(self, node):
node = asttyped.ModuleT(
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
body=node.body, loc=node.loc)
return self.visit(node)

try:
self.env_stack.append(node.typing_env)
return self.generic_visit(node)
finally:
self.env_stack.pop()

def visit_FunctionDef(self, node):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
@@ -213,7 +167,12 @@ def visit_FunctionDef(self, node):
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs,
loc=node.loc)
return self.visit(node)

try:
self.env_stack.append(node.typing_env)
return self.generic_visit(node)
finally:
self.env_stack.pop()

def visit_arg(self, node):
return asttyped.argT(type=self._find_name(node.arg, node.loc),
@@ -222,9 +181,9 @@ def visit_arg(self, node):

def visit_Num(self, node):
if isinstance(node.n, int):
typ = types.TInt()
typ = builtins.TInt()
elif isinstance(node.n, float):
typ = types.TFloat()
typ = builtins.TFloat()
else:
diag = diagnostic.Diagnostic("fatal",
"numeric type {type} is not supported", {"type": node.n.__class__.__name__},
@@ -239,19 +198,19 @@ def visit_Name(self, node):

def visit_NameConstant(self, node):
if node.value is True or node.value is False:
typ = types.TBool()
typ = builtins.TBool()
elif node.value is None:
typ = types.TNone()
typ = builtins.TNone()
return asttyped.NameConstantT(type=typ, value=node.value, 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]),
return asttyped.TupleT(type=builtins.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(),
node = asttyped.ListT(type=builtins.TList(),
elts=node.elts, ctx=node.ctx, loc=node.loc)
return self.visit(node)

@@ -297,92 +256,190 @@ def visit_Compare(self, node):
loc=node.loc)
return self.visit(node)

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

# expr
visit_Attribute = visit_unsupported
visit_BinOp = 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_Lambda = visit_unsupported
visit_ListComp = visit_unsupported
visit_Set = visit_unsupported
visit_SetComp = visit_unsupported
visit_Str = visit_unsupported
visit_Starred = visit_unsupported
visit_Yield = visit_unsupported
visit_YieldFrom = visit_unsupported

# stmt
visit_Assert = visit_unsupported
visit_ClassDef = visit_unsupported
visit_Delete = visit_unsupported
visit_Import = visit_unsupported
visit_ImportFrom = visit_unsupported
visit_Raise = visit_unsupported
visit_Try = visit_unsupported
visit_With = visit_unsupported


class Inferencer(algorithm.Visitor):
def __init__(self, engine):
self.engine = engine
self.function = None # currently visited function, for Return inference
self.in_loop = False

def _unify(self, typea, typeb, loca, locb, makenotes=None):
try:
typea.unify(typeb)
except types.UnificationError as e:
printer = types.TypePrinter()

if makenotes:
notes = makenotes(printer, typea, typeb, loca, locb)
else:
notes = [
diagnostic.Diagnostic("note",
"expression of type {typea}",
{"typea": printer.name(typea)},
loca),
diagnostic.Diagnostic("note",
"expression of type {typeb}",
{"typeb": printer.name(typeb)},
locb)
]

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

# makenotes for the case where types of multiple elements are unified
# with the type of parent expression
def _makenotes_elts(self, elts, kind):
def makenotes(printer, typea, typeb, loca, locb):
return [
diagnostic.Diagnostic("note",
"{kind} of type {typea}",
{"kind": kind, "typea": printer.name(elts[0].type)},
elts[0].loc),
diagnostic.Diagnostic("note",
"{kind} of type {typeb}",
{"kind": kind, "typeb": printer.name(typeb)},
locb)
]
return makenotes

def visit_ListT(self, node):
for elt in node.elts:
self._unify(node.type["elt"], elt.type,
node.loc, elt.loc, self._makenotes_elts(node.elts, "a list element"))
return node

def visit_SubscriptT(self, node):
# TODO: support more than just lists
self._unify(types.TList(node.type), node.value.type,
self._unify(builtins.TList(node.type), node.value.type,
node.loc, node.value.loc)
return node

def visit_IfExpT(self, node):
self._unify(node.body.type, node.orelse.type,
node.body.loc, node.orelse.loc)
node.type = node.body.type
return node

def visit_BoolOpT(self, node):
for value in node.values:
self._unify(node.type, value.type,
node.loc, value.loc, self._makenotes_elts(node.values, "an operand"))
return node

def visit_UnaryOpT(self, node):
if isinstance(node.op, ast.Not):
node.type = types.TBool()
node.type = builtins.TBool()
else:
operand_type = node.operand.type.find()
if types.is_numeric(operand_type):
if builtins.is_numeric(operand_type):
node.type = operand_type
elif not types.is_var(operand_type):
diag = diagnostic.Diagnostic("error",
"expected operand to be of numeric type, not {type}",
{"type": types.TypePrinter().name(operand_type)},
node.operand.loc)
self.engine.process(diag)
return node

def visit_ModuleT(self, node):
self.env_stack.append(node.typing_env)

node = self.generic_visit(node)

self.env_stack.pop()

return node

def visit_FunctionDefT(self, node):
self.env_stack.append(node.typing_env)
old_function, self.function = self.function, node

node = self.generic_visit(node)

self.function = old_function
self.env_stack.pop()

return node

def visit_Assign(self, node):
node = self.generic_visit(node)
self.generic_visit(node)
if len(node.targets) > 1:
self._unify(types.TTuple([x.type for x in node.targets]), node.value.type,
self._unify(builtins.TTuple([x.type for x in node.targets]), node.value.type,
node.targets[0].loc.join(node.targets[-1].loc), node.value.loc)
else:
self._unify(node.targets[0].type, node.value.type,
node.targets[0].loc, node.value.loc)
return node

def visit_AugAssign(self, node):
node = self.generic_visit(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)
old_in_loop, self.in_loop = self.in_loop, True
self.generic_visit(node)
self.in_loop = old_in_loop
# TODO: support more than just lists
self._unify(TList(node.target.type), node.iter.type,
self._unify(builtins.TList(node.target.type), node.iter.type,
node.target.loc, node.iter.loc)
return node

def visit_While(self, node):
old_in_loop, self.in_loop = self.in_loop, True
self.generic_visit(node)
self.in_loop = old_in_loop

def visit_Break(self, node):
if not self.in_loop:
diag = diagnostic.Diagnostic("error",
"break statement outside of a loop", {},
node.keyword_loc)
self.engine.process(diag)

def visit_Continue(self, node):
if not self.in_loop:
diag = diagnostic.Diagnostic("error",
"continue statement outside of a loop", {},
node.keyword_loc)
self.engine.process(diag)

def visit_FunctionDefT(self, node):
old_function, self.function = self.function, node
old_in_loop, self.in_loop = self.in_loop, False
self.generic_visit(node)
self.function = old_function
self.in_loop = old_in_loop

def visit_Return(self, node):
node = self.generic_visit(node)
if not self.function:
diag = diagnostic.Diagnostic("error",
"return statement outside of a function", {},
node.keyword_loc)
self.engine.process(diag)
return

self.generic_visit(node)
def makenotes(printer, typea, typeb, loca, locb):
return [
diagnostic.Diagnostic("note",
@@ -395,37 +452,12 @@ def makenotes(printer, typea, typeb, loca, locb):
node.loc)
]
if node.value is None:
self._unify(self.function.return_type, types.TNone(),
self._unify(self.function.return_type, builtins.TNone(),
self.function.name_loc, node.loc, makenotes)
else:
self._unify(self.function.return_type, node.value.type,
self.function.name_loc, node.value.loc, makenotes)

# 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_Call = visit_unsupported
visit_Compare = visit_unsupported
visit_Dict = visit_unsupported
visit_DictComp = visit_unsupported
visit_Ellipsis = visit_unsupported
visit_GeneratorExp = 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_Yield = visit_unsupported
visit_YieldFrom = visit_unsupported

class Printer(algorithm.Visitor):
def __init__(self, buf):
self.rewriter = source.Rewriter(buf)
@@ -467,7 +499,9 @@ def process_diagnostic(diag):

buf = source.Buffer("".join(fileinput.input()), os.path.basename(fileinput.filename()))
parsed, comments = parse_buffer(buf, engine=engine)
typed = Inferencer(engine=engine).visit(parsed)
typed = ASTTypedRewriter(engine=engine).visit(parsed)
Inferencer(engine=engine).visit(typed)

printer = Printer(buf)
printer.visit(typed)
for comment in comments:
19 changes: 19 additions & 0 deletions lit-test/py2llvm/typing/error_control_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# RUN: %python -m artiq.py2llvm.typing +diag %s >%t
# RUN: OutputCheck %s --file-to-check=%t

# CHECK-L: ${LINE:+1}: error: return statement outside of a function
return

# CHECK-L: ${LINE:+1}: error: break statement outside of a loop
break

# CHECK-L: ${LINE:+1}: error: continue statement outside of a loop
continue

while True:
def f():
# CHECK-L: ${LINE:+1}: error: break statement outside of a loop
break

# CHECK-L: ${LINE:+1}: error: continue statement outside of a loop
continue