From 7cd6011981240b01e9461ad99d51162199d09ff0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 26 Jun 2015 11:16:08 +0300 Subject: [PATCH] Add typechecking for most builtin. --- artiq/py2llvm/builtins.py | 16 +++- artiq/py2llvm/prelude.py | 7 +- artiq/py2llvm/typing.py | 107 +++++++++++++++++++---- lit-test/py2llvm/typing/builtin_calls.py | 30 +++++++ 4 files changed, 138 insertions(+), 22 deletions(-) diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index 90e222374..13ffe745d 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -31,6 +31,18 @@ class TList(types.TMono): elt = types.TVar() super().__init__("list", {"elt": elt}) +def fn_bool(): + return types.TBuiltin("class bool") + +def fn_int(): + return types.TBuiltin("class int") + +def fn_float(): + return types.TBuiltin("class float") + +def fn_list(): + return types.TBuiltin("class list") + def fn_len(): return types.TBuiltin("function len") @@ -80,7 +92,7 @@ def is_collection(typ): return isinstance(typ, types.TTuple) or \ types.is_mono(typ, "list") -def is_function(typ, name): +def is_builtin(typ, name): typ = typ.find() return isinstance(typ, types.TBuiltin) and \ - typ.name == "function " + name + typ.name == name diff --git a/artiq/py2llvm/prelude.py b/artiq/py2llvm/prelude.py index 4f603c05a..3ecd63a69 100644 --- a/artiq/py2llvm/prelude.py +++ b/artiq/py2llvm/prelude.py @@ -7,9 +7,10 @@ from . import builtins def globals(): return { - "bool": builtins.TBool(), - "int": builtins.TInt(), - "float": builtins.TFloat(), + "bool": builtins.fn_bool(), + "int": builtins.fn_int(), + "float": builtins.fn_float(), + "list": builtins.fn_list(), "len": builtins.fn_len(), "round": builtins.fn_round(), "range": builtins.fn_range(), diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index bd7145e09..10c063d55 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -716,62 +716,135 @@ class Inferencer(algorithm.Visitor): self._unify_collection(element=node.target, collection=node.iter) def visit_builtin_call(self, node): - if types.is_mono(node.func.type): - func_name = "function " + node.func.type.find().name - elif builtins.is_function(node.func.type): - func_name = node.func.type.find().name + typ = node.func.type.find() def valid_form(signature): return diagnostic.Diagnostic("note", "{func} can be invoked as: {signature}", - {"func": func_name}, + {"func": typ.name, "signature": signature}, node.func.loc) def diagnose(valid_forms): diag = diagnostic.Diagnostic("error", "{func} cannot be invoked with these arguments", - {"func": func_name}, + {"func": typ.name}, node.func.loc, notes=valid_forms) + self.engine.process(diag) - if builtins.is_bool(node.type): + if builtins.is_builtin(typ, "class bool"): valid_forms = lambda: [ valid_form("bool() -> bool"), - valid_form("bool(x:'a) -> bool where 'a is numeric") + valid_form("bool(x:'a) -> bool") ] - elif builtins.is_int(node.type): + + if len(node.args) == 0 and len(node.keywords) == 0: + pass # False + elif len(node.args) == 1 and len(node.keywords) == 0: + arg, = node.args + pass # anything goes + else: + diagnose(valid_forms()) + + self._unify(node.type, builtins.TBool(), + node.loc, None) + elif builtins.is_builtin(typ, "class int"): valid_forms = lambda: [ valid_form("int() -> int(width='a)"), valid_form("int(x:'a) -> int(width='b) where 'a is numeric"), - valid_form("int(x:'a, width='b ) -> int(width='b) where 'a is numeric") + valid_form("int(x:'a, width='b:) -> int(width='b) where 'a is numeric") ] - elif builtins.is_float(node.type): + + self._unify(node.type, builtins.TInt(), + node.loc, None) + + if len(node.args) == 0 and len(node.keywords) == 0: + pass # 0 + elif len(node.args) == 1 and len(node.keywords) == 0 and \ + builtins.is_numeric(node.args[0].type): + pass + elif len(node.args) == 1 and len(node.keywords) == 1 and \ + builtins.is_numeric(node.args[0].type) and \ + node.keywords[0].arg == 'width': + width = node.keywords[0].value + if not (isinstance(width, asttyped.NumT) and isinstance(width.n, int)): + diag = diagnostic.Diagnostic("error", + "the width argument of int() must be an integer literal", {}, + node.keywords[0].loc) + + self._unify(node.type, builtins.TInt(types.TValue(width.n)), + node.loc, None) + else: + diagnose(valid_forms()) + elif builtins.is_builtin(typ, "class float"): valid_forms = lambda: [ valid_form("float() -> float"), valid_form("float(x:'a) -> float where 'a is numeric") ] - elif builtins.is_list(node.type): + + self._unify(node.type, builtins.TFloat(), + node.loc, None) + + if len(node.args) == 0 and len(node.keywords) == 0: + pass # 0.0 + elif len(node.args) == 1 and len(node.keywords) == 0 and \ + builtins.is_numeric(node.args[0].type): + pass + else: + diagnose(valid_forms()) + elif builtins.is_builtin(typ, "class list"): valid_forms = lambda: [ valid_form("list() -> list(elt='a)"), # TODO: add this form when adding iterators # valid_form("list(x) -> list(elt='a)") ] - elif builtins.is_function(node.type, "len"): + + self._unify(node.type, builtins.TList(), + node.loc, None) + + if len(node.args) == 0 and len(node.keywords) == 0: + pass # [] + else: + diagnose(valid_forms()) + elif builtins.is_builtin(typ, "function len"): valid_forms = lambda: [ valid_form("len(x:list(elt='a)) -> int(width='b)"), ] - elif builtins.is_function(node.type, "round"): + + # TODO: should be ssize_t-sized + self._unify(node.type, builtins.TInt(types.TValue(32)), + node.loc, None) + + if len(node.args) == 1 and len(node.keywords) == 0: + arg, = node.args + + self._unify(arg.type, builtins.TList(), + arg.loc, None) + else: + diagnose(valid_forms()) + elif builtins.is_builtin(typ, "function round"): valid_forms = lambda: [ valid_form("round(x:float) -> int(width='a)"), ] + + self._unify(node.type, builtins.TInt(), + node.loc, None) + + if len(node.args) == 1 and len(node.keywords) == 0: + arg, = node.args + + self._unify(arg.type, builtins.TFloat(), + arg.loc, None) + else: + diagnose(valid_forms()) # TODO: add when there are range types - # elif builtins.is_function(node.type, "range"): + # elif builtins.is_builtin(typ, "function range"): # valid_forms = lambda: [ # valid_form("range(max:'a) -> range(elt='a)"), # valid_form("range(min:'a, max:'a) -> range(elt='a)"), # valid_form("range(min:'a, max:'a, step:'a) -> range(elt='a)"), # ] # TODO: add when it is clear what interface syscall() has - # elif builtins.is_function(node.type, "syscall"): + # elif builtins.is_builtin(typ, "function syscall"): # valid_Forms = lambda: [ # ] @@ -790,7 +863,7 @@ class Inferencer(algorithm.Visitor): if types.is_var(node.func.type): return # not enough info yet elif types.is_mono(node.func.type) or types.is_builtin(node.func.type): - return self.visit_builtin_call(self, node) + return self.visit_builtin_call(node) elif not types.is_function(node.func.type): diag = diagnostic.Diagnostic("error", "cannot call this expression of type {type}", diff --git a/lit-test/py2llvm/typing/builtin_calls.py b/lit-test/py2llvm/typing/builtin_calls.py index fb6ac38c0..8405391f9 100644 --- a/lit-test/py2llvm/typing/builtin_calls.py +++ b/lit-test/py2llvm/typing/builtin_calls.py @@ -1,2 +1,32 @@ # RUN: %python -m artiq.py2llvm.typing %s >%t # RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: bool:():bool +bool() + +# CHECK-L: bool:([]:list(elt='a)):bool +bool([]) + +# CHECK-L: int:():int(width='b) +int() + +# CHECK-L: int:(1.0:float):int(width='c) +int(1.0) + +# CHECK-L: int:(1.0:float, width=64:int(width='d)):int(width=64) +int(1.0, width=64) + +# CHECK-L: float:():float +float() + +# CHECK-L: float:(1:int(width='e)):float +float(1) + +# CHECK-L: list:():list(elt='f) +list() + +# CHECK-L: len:([]:list(elt='g)):int(width=32) +len([]) + +# CHECK-L: round:(1.0:float):int(width='h) +round(1.0)