def check_expr(expr, t, env): """ Check whether the expression `expr` can be assigned type `t` under type environment `env`. We defer to the specific `check_X_expr` functions to determine which type assignment rule to try. Information about the structure of each AST node is contained in the thesis PDF. """ assert isinstance(expr, ast.expr), \ "Should be typechecking an expr node, not a " + cname(expr) assert isinstance(t, PType), \ "Should be checking against a PType, not a " + cname(t) n = expr_template % expr.__class__.__name__ t_debug("-- v Typechecking expr as " + str(t) + " v --\nExpr: " + str(expr) + "\nEnv: " + str(env)) # if we get a KeyError, then we're inspecting an AST node that is not in # the subset of the language we're considering (note: the subset is # defined as whtaever there are check function definitions for). try: result = call_function(n, expr, t, env) t_debug("return: " + str(result) + "\n-- ^ Typechecking expr ^ --") return result except KeyError as e: t_debug("Found an expr not in the language subset. (" + str(e) + ")") return False
def __init__(self, targets, t, line, col=None): """ Create a `TypeDec` node with the supplied parameters. #### Parameters - `targets`: list of identifiers (as `ast.Name` objects) having their types declared. - `t`: the type being assigned, as a PType or string. If a string is provided, it is parsed into the appropriate PType. - `line`: the (int) line number of the declaration in the source code. - `col`: [optional] the (int) column number of the declaration in the source code. If not provided, then the column number will just be set as `None`. """ self.targets = targets self.lineno = line if col is not None: self.col_offset = col if type(t) == str: self.t = PType.from_str(t) assert self.t.__class__ == PType, \ ("Got a %s back from TypeSpecParser.parse, not a PType" % cname(self.t.__class__)) elif t.__class__ == PType: self.t = t else: assert False, ("t needs to be specified as str or PType, not " + cname(t)) # these are instance variables provided by AST nodes to allow traversal # / parsing of the nodes. self._fields = ("targets", "t") self._attributes = ("lineno", "col_offset")
def _check_UnaryOp_expr(unop, t, env): """Unary Operations.""" assert unop.__class__ is ast.UnaryOp op = unop.op e = unop.operand assert op.__class__ in unary_ops, "%s not in unary ops" % cname(op) # (Inv) assignment rule. if op.__class__ is ast.Invert and t == int_t: return check_expr(e, int_t, env) # (Uadd) assignment rule. elif op.__class__ in [ast.UAdd, ast.USub] and t in [int_t, float_t]: return check_expr(e, t, env) # (Not) assignment rule. elif op.__class__ is ast.Not and t == bool_t: return check_expr(e, bool_t, env) # No assignment rule found. else: return False
def _check_BoolOp_expr(boolop, t, env): """Boolean Operations.""" assert boolop.__class__ is ast.BoolOp op = boolop.op es = boolop.values assert op.__class__ in bool_ops, "%s not in bool ops" % cname(op) # (BoolOp) assignment rule. return all(check_expr(e, t, env) for e in es)
def __init__(self, targets, t, line, col = None): """ Create a `TypeDec` node with the supplied parameters. #### Parameters - `targets`: list of identifiers (as `ast.Name` objects) having their types declared. - `t`: the type being assigned, as a PType or string. If a string is provided, it is parsed into the appropriate PType. - `line`: the (int) line number of the declaration in the source code. - `col`: [optional] the (int) column number of the declaration in the source code. If not provided, then the column number will just be set as `None`. """ self.targets = targets self.lineno = line if col is not None: self.col_offset = col if type(t) == str: self.t = PType.from_str(t) assert self.t.__class__ == PType, \ ("Got a %s back from TypeSpecParser.parse, not a PType" % cname(self.t.__class__)) elif t.__class__ == PType: self.t = t else: assert False, ("t needs to be specified as str or PType, not " + cname(t)) # these are instance variables provided by AST nodes to allow traversal # / parsing of the nodes. self._fields = ("targets", "t") self._attributes = ("lineno", "col_offset")
def infer_expr(e, env): """ Use limited type inference to determine the type of AST expression `e` under type environment `env`. """ assert isinstance(e, ast.expr), \ "Should be inferring type of an expr node, not a " + cname(e) n = get_infer_expr_func_name(e.__class__.__name__) # If we get a KeyError, then we're trying to infer the type of an AST node # that is not in the very limited subset of the language that we're trying # to perform type inference on. return call_function(n, e, env)
def _check_BinOp_expr(binop, t, env): """Binary Operations.""" assert binop.__class__ is ast.BinOp e0 = binop.left e1 = binop.right op = binop.op assert op.__class__ in bin_ops, "%s not in binary ops" % cname(op) # Numeric Operations. if t == int_t or t == float_t: # (Arith) assignment rule. if op.__class__ in arith_ops: return check_expr(e0, t, env) and check_expr(e1, t, env) # (BitOp) assignment rule. elif op.__class__ in bit_ops and t == int_t: return check_expr(e0, int_t, env) and check_expr(e1, int_t, env) # String and List Operations. elif t == str_t or t == unicode_t or t.is_list(): # (Str-Cat) assignment rule. if op.__class__ is ast.Add: return check_expr(e0, t, env) and check_expr(e1, t, env) # (Str-Rep) assignment rule. elif op.__class__ is ast.Mult: return any(check_expr(l, int_t, env) and check_expr(r, t, env) for (l, r) in [(e0, e1), (e1, e0)]) # (Str-Form) assignment rule. elif not t.is_list() and op.__class__ is ast.Mod: return check_expr(e0, t, env) # Tuple Operations. elif t.is_tuple(): # (Tup-Cat) assignment rule. if op.__class__ is ast.Add: return any(check_expr(e0, t.tuple_slice(0, m), env) and check_expr(e1, t.tuple_slice(m), env) for m in range(t.tuple_len())) # (Tup-Rep) assignment rule. elif (op.__class__ is ast.Mult and any(e.__class__ is ast.Num for e in [e0,e1])): m = e0.n if e0.__class__ is ast.Num else e1.n e = e0 if e1.__class__ is ast.Num else e1 e_len = int(t.tuple_len() / m) e_t = t.tuple_slice(0, e_len) return (type(m) is int and t.tuple_len() % m == 0 and check_expr(e, e_t, env) and all(e_t == t.tuple_slice(e_len*i, e_len*(i+1)) for i in range(1, m))) # No assignment rule found. return False