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: eb76f594a091
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: 7f77632f1a15
Choose a head ref
  • 3 commits
  • 7 files changed
  • 1 contributor

Commits on Jun 6, 2015

  1. Add support for NameConstant.

    whitequark committed Jun 6, 2015
    Copy the full SHA
    d08598f View commit details
  2. Add support for Return.

    whitequark committed Jun 6, 2015
    Copy the full SHA
    5f06c6a View commit details
  3. Add lit-based tests for type inferencer.

    whitequark committed Jun 6, 2015
    Copy the full SHA
    7f77632 View commit details
Showing with 160 additions and 35 deletions.
  1. +1 −0 .gitignore
  2. +9 −2 artiq/py2llvm/types.py
  3. +88 −32 artiq/py2llvm/typing.py
  4. +23 −0 lit-test/harness.py
  5. +12 −0 lit-test/lit.cfg
  6. +26 −0 lit-test/py2llvm/typing/unify.py
  7. +1 −1 setup.py
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -13,3 +13,4 @@ doc/manual/_build
/*.egg-info
/.coverage
examples/master/results
Output/
11 changes: 9 additions & 2 deletions artiq/py2llvm/types.py
Original file line number Diff line number Diff line change
@@ -155,6 +155,10 @@ 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")
@@ -193,8 +197,11 @@ def name(self, typ):
self.map[typ] = "'%s" % next(self.gen)
return self.map[typ]
elif isinstance(typ, TMono):
return "%s(%s)" % (typ.name, ", ".join(
["%s=%s" % (k, self.name(typ.params[k])) for k in typ.params]))
if typ.params == {}:
return typ.name
else:
return "%s(%s)" % (typ.name, ", ".join(
["%s=%s" % (k, self.name(typ.params[k])) for k in typ.params]))
elif isinstance(typ, TTuple):
if len(typ.elts) == 1:
return "(%s,)" % self.name(typ.elts[0])
120 changes: 88 additions & 32 deletions artiq/py2llvm/typing.py
Original file line number Diff line number Diff line change
@@ -129,29 +129,41 @@ def visit_ExceptHandler(self, node):
class Inferencer(algorithm.Transformer):
def __init__(self, engine):
self.engine = engine
self.env_stack = [{}]
self.env_stack = []
self.function = None # currently visited function

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

if kind == "generic":
if kind == "expects":
note1 = diagnostic.Diagnostic("note",
"expression of type {typea}",
"expression expecting an operand of type {typea}",
{"typea": printer.name(typea)},
loca)
elif kind == "expects":
elif kind == "return_type" or kind == "return_type_none":
note1 = diagnostic.Diagnostic("note",
"expression expecting an operand of type {typea}",
"function with return type {typea}",
{"typea": printer.name(typea)},
loca)
else:
note1 = diagnostic.Diagnostic("note",
"expression of type {typea}",
{"typea": printer.name(typea)},
loca)

note2 = diagnostic.Diagnostic("note",
"expression of type {typeb}",
{"typeb": printer.name(typeb)},
locb)
if kind == "return_type_none":
note2 = diagnostic.Diagnostic("note",
"implied expression of type {typeb}",
{"typeb": printer.name(typeb)},
locb)
else:
note2 = diagnostic.Diagnostic("note",
"expression of type {typeb}",
{"typeb": printer.name(typeb)},
locb)

if e.typea.find() == typea.find() and e.typeb.find() == typeb.find():
diag = diagnostic.Diagnostic("fatal",
@@ -166,37 +178,60 @@ def _unify(self, typea, typeb, loca, locb, kind):
loca, [locb], notes=[note1, note2])
self.engine.process(diag)

def _find_name(self, name, loc):
for typing_env in reversed(self.env_stack):
if name in typing_env:
return typing_env[name]
diag = diagnostic.Diagnostic("fatal",
"name '{name}' is not bound to anything", {"name":name}, loc)
self.engine.process(diag)

def visit_root(self, node):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
self.env_stack.append(extractor.typing_env)

return self.visit(node)

# 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),
arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc)

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

self.env_stack.append(extractor.typing_env)

node = asttyped.FunctionDefT(
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
name=node.name, args=self.visit(node.args), returns=self.visit(node.returns),
body=[self.visit(x) for x in node.body], decorator_list=node.decorator_list,
return_type=types.TVar(),

name=node.name, args=node.args, returns=node.returns,
body=node.body, decorator_list=node.decorator_list,
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)

old_function, self.function = self.function, node
self.generic_visit(node)
self.function = old_function

self.env_stack.pop()

return node

def _find_name(self, name, loc):
for typing_env in reversed(self.env_stack):
if name in typing_env:
return typing_env[name]
diag = diagnostic.Diagnostic("fatal",
"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),
arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc)
def visit_Return(self, node):
node = self.generic_visit(node)
if node.value is None:
self._unify(self.function.return_type, types.TNone(),
self.function.name_loc, node.value.loc, kind="return_type_none")
else:
self._unify(self.function.return_type, node.value.type,
self.function.name_loc, node.value.loc, kind="return_type")

def visit_Num(self, node):
if isinstance(node.n, int):
@@ -215,6 +250,13 @@ 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_NameConstant(self, node):
if node.value is True or node.value is False:
typ = types.TBool()
elif node.value is None:
typ = types.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]),
@@ -300,25 +342,39 @@ def __init__(self, buf):
def rewrite(self):
return self.rewriter.rewrite()

def visit_FunctionDefT(self, node):
self.rewriter.insert_before(node.colon_loc,
"->{}".format(self.type_printer.name(node.return_type)))

super().generic_visit(node)

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

super().generic_visit(node)

def main():
import sys, fileinput
import sys, fileinput, os

inference_mode = True

engine = diagnostic.Engine(all_errors_are_fatal=True)
try:
buf = source.Buffer("".join(fileinput.input()), fileinput.filename())
parsed = parse_buffer(buf, engine=engine)
typed = Inferencer(engine=engine).visit(parsed)
buf = source.Buffer("".join(fileinput.input()), os.path.basename(fileinput.filename()))
parsed, comments = parse_buffer(buf, engine=engine)
typed = Inferencer(engine=engine).visit_root(parsed)
printer = Printer(buf)
printer.visit(typed)
for comment in comments:
if comment.text.find("CHECK") >= 0:
printer.rewriter.remove(comment.loc)
print(printer.rewrite().source)
except diagnostic.Error as e:
print("\n".join(e.diagnostic.render()), file=sys.stderr)
if inference_mode:
print("\n".join(e.diagnostic.render()), file=sys.stderr)
exit(1)

if __name__ == "__main__":
main()
23 changes: 23 additions & 0 deletions lit-test/harness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
The purpose of this harness is to emulate the behavior of
the python executable, but add the ARTIQ root to sys.path
beforehand.
This is necessary because eggs override the PYTHONPATH environment
variable, but not current directory; therefore `python -m artiq...`
ran from the ARTIQ root would work, but there is no simple way to
emulate the same behavior when invoked under lit.
"""

import sys, os, argparse, importlib

parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('-m', metavar='mod', type=str, help='run library module as a script')
parser.add_argument('args', type=str, nargs='+', help='arguments passed to program in sys.argv[1:]')
args = parser.parse_args(sys.argv[1:])

artiq_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(1, artiq_path)

sys.argv[1:] = args.args
importlib.import_module(args.m).main()
12 changes: 12 additions & 0 deletions lit-test/lit.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import lit.util
import lit.formats

config.name = 'ARTIQ'
config.test_format = lit.formats.ShTest()
config.suffixes = ['.py']
config.excludes = ['harness.py']
config.test_source_root = os.path.dirname(__file__)

python_executable = 'python3'
harness = '{} {}'.format(python_executable, os.path.join(config.test_source_root, 'harness.py'))
config.substitutions.append( ('%python', harness) )
26 changes: 26 additions & 0 deletions lit-test/py2llvm/typing/unify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# RUN: %python -m artiq.py2llvm.typing %s >%t
# RUN: OutputCheck %s --file-to-check=%t

a = 1
# CHECK-L: a:int(width='a)

b = a
# CHECK-L: b:int(width='a)

c = True
# CHECK-L: c:bool

d = False
# CHECK-L: d:bool

e = None
# CHECK-L: e:NoneType

f = 1.0
# CHECK-L: f:float

g = []
# CHECK-L: g:list(elt='b)

h = [1]
# CHECK-L: h:list(elt=int(width='c))
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
requirements = [
"sphinx", "sphinx-argparse", "pyserial", "numpy", "scipy",
"python-dateutil", "prettytable", "h5py", "pydaqmx", "pyelftools",
"quamash", "pyqtgraph", "pythonparser"
"quamash", "pyqtgraph", "pythonparser", "lit", "OutputCheck"
]

scripts = [