def assign_single_target(target, assigned_type, static_value, context): target_token = expr.get_token(target) if target_token == 'Name': old_symbol = context.get(target.id) new_symbol = Symbol(target.id, assigned_type, static_value) context.add(new_symbol) return (target.id, old_symbol, new_symbol) elif target_token == 'Subscript': _ = expr.expression_type(target.value, context) # TODO: implement this return ('Subscript', None, None) elif target_token == 'Attribute': instance = expr.expression_type(target.value, context) if not isinstance(instance, Instance): return (target.attr, None, None) else: old_symbol = instance.attributes.get(target.attr) new_symbol = Symbol(target.attr, assigned_type, static_value) instance.attributes.add(new_symbol) return (target.attr, old_symbol, new_symbol) elif target_token == 'Tuple': # TODO: implement this return ('Tuple', None, None) else: raise RuntimeError('Unrecognized assignment target: ' + target_token)
def generic_scope(self): scope = Scope() if self.name is not None: # for recursive calls function_type = Function(self, Unknown(), NullEvaluator()) scope.add(Symbol(self.name, function_type)) for name, argtype in self.get_dict().items(): scope.add(Symbol(name, argtype)) if self.vararg_name: scope.add(Symbol(self.vararg_name, List(Unknown()))) if self.kwarg_name: scope.add(Symbol(self.kwarg_name, Dict(Unknown(), Unknown()))) return scope
def evaluate(self, argument_scope): # argument_scope does not contain "self" parameter at this point # because we create the "self" instance inside this method instance = Instance(self._class_object.name, Scope()) class_attributes = self._class_object.attributes for name in class_attributes.names(): symbol_type = class_attributes.get_type(name) if isinstance(symbol_type, Function): function_type = Function( symbol_type.signature, symbol_type.return_type, symbol_type.evaluator, instance) instance.attributes.add(Symbol(name, function_type)) # Note: error checking for arguments passed in has already been # handled because the signature for the class object is loaded # based on the signature of the __init__ function init_function_type = instance.attributes.get_type('__init__') if init_function_type is not None: symbol = Symbol(init_function_type.signature.names[0], instance) argument_scope.add(symbol) init_function_type.evaluator.evaluate(argument_scope) instance.initialized = True return instance, UnknownValue()
def maybe_inferences(test, context): types = {name: context.get_type(name) for name in get_names(test)} maybes = {k: v for k, v in types.items() if isinstance(v, Maybe)} if_inferences = {} else_inferences = {} for name, maybe_type in maybes.items(): context.begin_scope() context.add(Symbol(name, NoneType(), None)) none_value = static_evaluate(test, context) context.end_scope() if none_value is False: if_inferences[name] = maybe_type.subtype if none_value is True: else_inferences[name] = maybe_type.subtype context.begin_scope() context.add(Symbol(name, maybe_type.subtype, UnknownValue())) non_none_value = static_evaluate(test, context) context.end_scope() if non_none_value is False: if_inferences[name] = NoneType() if non_none_value is True: else_inferences[name] = NoneType() return if_inferences, else_inferences
def construct_function_type(functiondef_node, visitor, instance=None): name = getattr(functiondef_node, 'name', None) signature = FunctionSignature(name, functiondef_node.args, visitor.context()) body = functiondef_node.body first_visitor = (visitor if instance is None or (name == '__init__' and not instance.initialized) or (name != '__init__' and instance.initialized) else visitor.clone()) first_evaluator = FunctionEvaluator(body, first_visitor) first_visitor.context().clear_constraints() argument_scope = signature.generic_scope() if instance is not None: self_symbol = Symbol(signature.names[0], instance) argument_scope.add(self_symbol) return_type, _ = first_evaluator.evaluate(argument_scope) signature.constrain_types(first_visitor.context().get_constraints()) evaluator = FunctionEvaluator(body, visitor.clone()) return Function(signature, return_type, evaluator)
def _visit_expression(node, expected_type, context, warnings): recur = partial(visit_expression, context=context, warnings=warnings) probe = partial(expression_type, context=context) comp = partial(comprehension_type, context=context, warnings=warnings) token = get_token(node) if token == 'BoolOp': for expr in node.values: recur(expr, Bool()) return Bool() # more restrictive than Python if token == 'BinOp': operator = get_token(node.op) if operator == 'Add': left_probe = probe(node.left) right_probe = probe(node.right) if isinstance(left_probe, Tuple) or isinstance(right_probe, Tuple): left = recur(node.left, BaseTuple()) right = recur(node.right, BaseTuple()) if isinstance(left, Tuple) and isinstance(right, Tuple): return Tuple(left.item_types + right.item_types) else: return Unknown() union_type = Union(Num(), Str(), List(Unknown())) left_intersect = type_intersection(left_probe, union_type) right_intersect = type_intersection(right_probe, union_type) sub_intersect = type_intersection(left_intersect, right_intersect) full_intersect = type_intersection(expected_type, sub_intersect) intersect = (full_intersect or sub_intersect or left_intersect or right_intersect or union_type) recur(node.left, intersect) recur(node.right, intersect) return intersect elif operator == 'Mult': union_type = Union(Num(), Str()) expected_intersect = type_intersection(expected_type, union_type) left_intersect = type_intersection(probe(node.left), union_type) right = recur(node.right, Num()) if isinstance(left_intersect, Num): recur(node.left, Num()) return Num() elif isinstance(left_intersect, Str): recur(node.left, Str()) return Str() elif isinstance(expected_intersect, Num): recur(node.left, Num()) return Num() elif isinstance(expected_intersect, Str): recur(node.left, Str()) return Str() else: recur(node.left, union_type) return union_type elif operator == 'Mod': # num % num OR str % unknown union_type = Union(Num(), Str()) expected_intersect = type_intersection(expected_type, union_type) left_intersect = type_intersection(probe(node.left), union_type) if isinstance(left_intersect, Num): recur(node.left, Num()) recur(node.right, Num()) return Num() elif isinstance(left_intersect, Str): recur(node.left, Str()) recur(node.right, Unknown()) return Str() elif isinstance(expected_intersect, Num): recur(node.left, Num()) recur(node.right, Num()) return Num() elif isinstance(expected_intersect, Str): recur(node.left, Str()) recur(node.right, Unknown()) return Str() else: recur(node.left, union_type) recur(node.right, Unknown()) return union_type else: recur(node.left, Num()) recur(node.right, Num()) return Num() if token == 'UnaryOp': if get_token(node.op) == 'Not': recur(node.operand, Bool()) return Bool() else: recur(node.operand, Num()) return Num() if token == 'Lambda': return construct_function_type(node, LambdaVisitor(context)) if token == 'IfExp': recur(node.test, Bool()) if_inferences, else_inferences = maybe_inferences(node.test, context) context.begin_scope(Scope(if_inferences)) body_type = recur(node.body, expected_type) context.end_scope() context.begin_scope(Scope(else_inferences)) else_type = recur(node.orelse, expected_type) context.end_scope() return unify_types([body_type, else_type]) if token == 'Dict': key_type = unify_types([recur(key, Unknown()) for key in node.keys]) value_type = unify_types( [recur(value, Unknown()) for value in node.values]) return Dict(key_type, value_type) if token == 'Set': subtype = (expected_type.item_type if isinstance(expected_type, Set) else Unknown()) return Set(unify_types([recur(elt, Unknown()) for elt in node.elts])) if token == 'ListComp': subtype = (expected_type.item_type if isinstance(expected_type, List) else Unknown()) return List(comp(node.elt, node.generators, subtype)) if token == 'SetComp': subtype = (expected_type.item_type if isinstance(expected_type, Set) else Unknown()) return Set(comp(node.elt, node.generators, subtype)) if token == 'DictComp': expected_key_type = (expected_type.key_type if isinstance( expected_type, Dict) else Unknown()) expected_value_type = (expected_type.value_type if isinstance( expected_type, Dict) else Unknown()) key_type = comp(node.key, node.generators, expected_key_type) value_type = comp(node.value, node.generators, expected_value_type) return Dict(key_type, value_type) if token == 'GeneratorExp': subtype = (expected_type.item_type if isinstance(expected_type, List) else Unknown()) return List(comp(node.elt, node.generators, subtype)) if token == 'Yield': return List(recur(node.value, Unknown())) if token == 'Compare': operator = get_token(node.ops[0]) if len(node.ops) > 1 or len(node.comparators) > 1: warnings.warn(node, 'comparison-operator-chaining') if operator in ['Eq', 'NotEq', 'Lt', 'LtE', 'Gt', 'GtE']: # all operands are constrained to have the same type # as their intersection left_probe = probe(node.left) right_probe = probe(node.comparators[0]) intersection = type_intersection(left_probe, right_probe) if intersection is None: recur(node.left, right_probe) recur(node.comparators[0], left_probe) else: recur(node.left, intersection) recur(node.comparators[0], intersection) if operator in ['Is', 'IsNot']: recur(node.left, Maybe(Unknown())) recur(node.comparators[0], NoneType()) if operator in ['In', 'NotIn']: # constrain right to list/set of left, and left to inst. of right left_probe = probe(node.left) right_probe = probe(node.comparators[0]) union_type = Union(List(left_probe), Set(left_probe), Dict(left_probe, Unknown()), Str()) recur(node.comparators[0], union_type) if isinstance(right_probe, (List, Set)): recur(node.left, right_probe.item_type) elif isinstance(right_probe, Dict): recur(node.left, right_probe.key_type) else: recur(node.left, Unknown()) return Bool() if token == 'Call': function_type = recur(node.func, Unknown()) if not isinstance(function_type, (Class, Function)): if not isinstance(function_type, Unknown): warnings.warn(node, 'not-a-function') return Unknown() signature = function_type.signature instance = (function_type.instance if isinstance( function_type, Function) else None) offset = 1 if (instance is not None or isinstance(function_type, Class)) else 0 argument_scope = Scope() if instance is not None: self_symbol = Symbol(signature.names[0], instance) argument_scope.add(self_symbol) # make sure all required arguments are specified if node.starargs is None and node.kwargs is None: start = offset + len(node.args) required = signature.names[start:signature.min_count] kwarg_names = [keyword.arg for keyword in node.keywords] missing = [name for name in required if name not in kwarg_names] for missing_argument in missing: warnings.warn(node, 'missing-argument', missing_argument) # check for too many arguments if signature.vararg_name is None: if len(node.args) + len(node.keywords) > len(signature.types): warnings.warn(node, 'too-many-arguments') # load positional arguments for i, arg in enumerate(node.args): if i + offset >= len(signature): break arg_type = recur(arg, signature.types[i + offset]) value = static_evaluate(arg, context) argument_scope.add( Symbol(signature.names[i + offset], arg_type, value)) # load keyword arguments for kwarg in node.keywords: # TODO: make sure there is no overlap with positional args expected_type = signature.get_dict().get(kwarg.arg) if expected_type is None: warnings.warn(node, 'extra-keyword', kwarg.arg) else: arg_type = recur(kwarg.value, expected_type) value = static_evaluate(kwarg.value, context) argument_scope.add(Symbol(kwarg.arg, arg_type, value)) if node.starargs is not None: recur(node.starargs, List(Unknown())) if node.kwargs is not None: recur(node.kwargs, Dict(Unknown(), Unknown())) return_type, _ = function_type.evaluator.evaluate(argument_scope) return return_type if token == 'Repr': return Str() if token == 'Num': return Num() if token == 'Str': return Str() if token == 'Attribute': value_type = recur(node.value, Unknown()) if isinstance(value_type, Unknown): return Unknown() if not isinstance(value_type, Instance): warnings.warn(node, 'not-an-instance') return Unknown() attr_type = value_type.attributes.get_type(node.attr) if attr_type is None: warnings.warn(node, 'not-a-member') return Unknown() return attr_type if token == 'Subscript': union_type = Union(List(Unknown()), Dict(Unknown(), Unknown()), BaseTuple()) value_type = recur(node.value, union_type) if get_token(node.slice) == 'Index': if isinstance(value_type, Tuple): index = static_evaluate(node.slice.value, context) if isinstance(index, UnknownValue): return Unknown() if not isinstance(index, int): return Unknown() if not 0 <= index < len(value_type.item_types): return Unknown() return value_type.item_types[index] elif isinstance(value_type, List): return value_type.item_type elif isinstance(value_type, Dict): return value_type.value_type else: return Unknown() elif get_token(node.slice) == 'Slice': if node.slice.lower is not None: recur(node.slice.lower, Num()) if node.slice.upper is not None: recur(node.slice.upper, Num()) if node.slice.step is not None: recur(node.slice.step, Num()) return value_type else: return value_type if token == 'Name': defined_type = context.get_type(node.id) if defined_type is None: warnings.warn(node, 'undefined', node.id) context.add_constraint(node.id, expected_type) return defined_type or Unknown() if token == 'List': subtype = (expected_type.item_type if isinstance(expected_type, List) else Unknown()) return List(unify_types([recur(elt, subtype) for elt in node.elts])) if token == 'Tuple': if (isinstance(expected_type, Tuple) and len(node.elts) == len(expected_type.item_types)): return Tuple([ recur(element, type_) for element, type_ in zip(node.elts, expected_type.item_types) ]) return Tuple([recur(element, Unknown()) for element in node.elts]) raise Exception('visit_expression does not recognize ' + token)
def visit(self, expression): result_type = expression_type(expression, self._context) symbol = Symbol('', result_type) self._context.set_return(symbol)