def close(code_block, env): """@types: list[ast.AST], LinkedList -> LinkedList""" for e in code_block: if IS(e, FunctionDef): c = Closure(e, nil) env = ext(e.name, [c], env) elif IS(e, ClassDef): c = ClassType(e.name, e.bases, e.body, nil, e) env = ext(e.name, [c], env) # here we also need Import and Assign # Assign is complicated return env
def mergeEnv(env1, env2): ret = nil for p1 in env1: p2 = assq(first(p1), env2) if p2 != None: ret = ext(first(p1), union([rest(p1), rest(p2)]), ret) return ret
def bind(target, infered_value, env): if IS(target, Name) or IS(target, str): u = infered_value putInfo(target, u) return ext(getId(target), u, env) elif IS(target, Attribute): ast_name = target.value infered_targets = infer(ast_name, env, nil) for obj in infered_targets: if IS(obj, (ClassType, ObjType)): obj.attrs[target.attr] = infered_value elif IS(obj, AttrType): obj.obj.attrs[target.attr] = infered_value else: error("Syntax error: wrong target type in assignment: ", obj, type(obj)) return env elif IS(target, Tuple) or IS(target, List): infered_values = infered_value target_to_value = defaultdict(list) for infered_value in infered_values: if IS(infered_value, TupleType) or IS(infered_value, List): debug("infered key, value:", infered_value) if len(target.elts) == len(infered_value.elts): for i in xrange(len(infered_value.elts)): target_to_value[target.elts[i]].extend(infered_value.elts[i]) elif len(target.elts) < len(infered_value.elts): putInfo(target, ValueError("too many values to unpack")) else: putInfo(target, ValueError("too few values to unpack")) else: putInfo(target, TypeError("non-iterable object")) for key, value in target_to_value.iteritems(): debug("binding %s to %s:" % (key, value)) env = bind(key, value, env) return env elif IS(target, ast.Subscript): error("Syntax error: Subscript type is not supported in assignment: ", target) return env else: putInfo(target, SyntaxError("not assignable")) return env
def invokeClosure(call, actualParams, clo, env, stk): """ @types: ast.Call, list[ast.AST], Closure, LinkedList, LinkedList -> list[Type] """ debug("invoking closure", clo.func, "with args", actualParams) debug(clo.func.body) func = clo.func fenv = clo.env pos = nil kwarg = nil # bind positionals first poslen = min(len(func.args.args), len(actualParams)) for i in xrange(poslen): t = infer(actualParams[i], env, stk) pos = bind(func.args.args[i], t, pos) # put extra positionals into vararg if provided # report error and go on otherwise if len(actualParams) > len(func.args.args): if func.args.vararg == None: err = TypeError("excess arguments to function") putInfo(call, err) return [err] else: ts = [] for i in xrange(len(func.args.args), len(actualParams)): t = infer(actualParams[i], env, stk) ts = ts + t pos = bind(func.args.vararg, ts, pos) # bind keywords, collect kwarg ids = map(getId, func.args.args) for k in call.keywords: ts = infer(k.value, env, stk) tloc1 = lookup(k.arg, pos) if tloc1 != None: putInfo(call, TypeError("multiple values for keyword argument", k.arg, tloc1)) elif k.arg not in ids: kwarg = bind(k.arg, ts, kwarg) else: pos = bind(k.arg, ts, pos) # put extras in kwarg or report them # bind call.keywords to func.args.kwarg if kwarg != nil: if func.args.kwarg != None: pos = bind(func.args.kwarg, [DictType(reverse(kwarg))], pos) else: putInfo(call, TypeError("unexpected keyword arguements", kwarg)) elif func.args.kwarg != None: pos = bind(func.args.kwarg, [DictType(nil)], pos) # bind defaults, avoid overwriting bound vars # types for defaults are already inferred when the function was defined i = len(func.args.args) - len(func.args.defaults) _ = len(func.args.args) for j in xrange(len(clo.defaults)): tloc = lookup(getId(func.args.args[i]), pos) if tloc == None: pos = bind(func.args.args[i], clo.defaults[j], pos) i += 1 # finish building the input type fromtype = maplist(lambda p: SimplePair(first(p), typeOnly(rest(p))), pos) # check whether the same call site is on stack with same input types # if so, we are back to a loop, terminate if onStack(call, fromtype, stk): return [bottomType] # push the call site onto the stack and analyze the function body stk = ext(call, fromtype, stk) fenv = append(pos, fenv) to = infer(func.body, fenv, stk) # record the function type putInfo(func, FuncType(reverse(fromtype), to)) return to
def invoke1(call, clo, env, stk): """@types: ast.Call, Callable, LinkedList, LinkedList -> ast.AST or Type """ if clo == bottomType: return [bottomType] # Even if operator is not a closure, resolve the # arguments for partial information. if not IS(clo, (Closure, ClassType, AttrType)): # infer arguments even if it is not callable # (we don't know which method it is) debug("Unknown function or method, infering arguments", call.args) for a in call.args: infer(a, env, stk) for k in call.keywords: infer(k.value, env, stk) err = TypeError("calling non-callable", clo) putInfo(call, err) return [err] if IS(clo, ClassType): debug("creating instance of", clo) ctorargs = [infer(arg, env, stk) for arg in call.args] new_obj = ObjType(clo, ctorargs, clo.env, call) init_closures = new_obj.attrs.get("__init__", []) if len(init_closures): # we don't really care about this name, # we just don't want to collide with method's global symbols # TODO: generate a special name, # that would represent a temporary object self_arg = get_self_arg_name(init_closures[0].func) ref_to_init = AttrType(init_closures, new_obj, self_arg) init_env = ext(self_arg.id, [new_obj], env) invoke1(call, ref_to_init, init_env, stk) return [new_obj] if IS(clo, AttrType): attr = clo if IS(attr.obj, ObjType): # add self to function call args actualParams = list(call.args) classtype = attr.obj.classtype saveMethodInvocationInfo(call, attr, env, stk) # TODO: @staticmethod, @classmethod if classtype.name != "module": actualParams.insert(0, attr.objT) types = [] for closure in attr.clo: if IS(closure, Closure): # Create new env for method # On each call ClassType.env might be different new_closure = Closure(closure.func, classtype.env) debug("invoking method", closure.func.name, "with args", call.args) types.extend(invokeClosure(call, actualParams, new_closure, env, stk)) elif IS(closure, ClassType): types.extend(invoke1(call, closure, env, stk)) else: types.append(TypeError("Callable type %s is not supported" % closure)) return types elif IS(attr.obj, DictType): types = [] for closure in attr.clo: if closure in attr.obj.iter_operations: types.extend(closure(attr.obj.dict)) else: # we take the first version of infered arguments # but ideally all must be processed infered_args = [infer(arg, env, stk) for arg in call.args] types.extend(closure(attr.obj.dict, *infered_args)) return types else: err = TypeError("AttrType object is not supported for the invoke", attr) putInfo(call, err) return [err] return invokeClosure(call, call.args, clo, env, stk)
def infer(exp, env, stk): "@types: ast.AST|object, LinkedList, LinkedList -> list[Type]" debug("infering", exp, exp.__class__) assert exp is not None if IS(exp, Module): return infer(exp.body, env, stk) elif IS(exp, list): env = close(exp, env) (t, _) = inferSeq(exp, env, stk) # env ignored (out of scope) return t elif IS(exp, Num): # we need objects, not types return [exp] # [PrimType(type(exp.n))] elif IS(exp, Str): # we need objects, not types return [exp] # [PrimType(type(exp.s))] elif IS(exp, Name): b = lookup(exp.id, env) debug("infering name:", b, env) if b != None: putInfo(exp, b) return b else: try: t = eval(exp.id) # try use information from Python interpreter return [PrimType(t)] except NameError as _: putInfo(exp, UnknownType(exp)) return [UnknownType(exp)] elif IS(exp, Lambda): c = Closure(exp, env) for d in exp.args.defaults: dt = infer(d, env, stk) c.defaults.append(dt) return [c] elif IS(exp, Call): return invoke(exp, env, stk) elif IS(exp, Attribute): t = infer(exp.value, env, stk) if t: attribs = [] # find attr name in object and return it for o in t: if not IS(o, (ObjType, ClassType, DictType)): attribs.append(TypeError("unknown object", o)) continue if exp.attr in o.attrs: attribs.append(AttrType(o.attrs[exp.attr], o, exp.value)) else: attribs.append(TypeError("no such attribute", exp.attr)) return attribs else: return [UnknownType(exp)] ## ignore complex types for now # elif IS(exp, List): # eltTypes = [] # for e in exp.elts: # t = infer(e, env, stk) # eltTypes.append(t) # return [Bind(ListType(eltTypes), exp)] # elif IS(exp, Tuple): # eltTypes = [] # for e in exp.elts: # t = infer(e, env, stk) # eltTypes.append(t) # return [Bind(TupleType(eltTypes), exp)] elif IS(exp, ObjType): return exp elif IS(exp, ast.List): infered_elts = flatten([infer(el, env, stk) for el in exp.elts]) return [ListType(tuple(infered_elts))] elif IS(exp, ast.Dict): infered_keys = [infer(key, env, stk) for key in exp.keys] infered_values = [infer(value, env, stk) for value in exp.values] temp_dict = defaultdict(list) dic = nil for keys, value in zip(infered_keys, infered_values): for key in keys: try: temp_dict[key] = value # only the last value # with the same key is stored except TypeError, _: # unhashable instance dic = ext(key, value, dic) for key, value in temp_dict.iteritems(): dic = ext(key, value, dic) return [DictType(dic)]