def _break_check(context, value_scope, flow_scope, node): reachable = REACHABLE if flow_scope.type == 'if_stmt': if flow_scope.is_node_after_else(node): for check_node in flow_scope.get_test_nodes(): reachable = _check_if(context, check_node) if reachable in (REACHABLE, UNSURE): break reachable = reachable.invert() else: flow_node = flow_scope.get_corresponding_test_node(node) if flow_node is not None: reachable = _check_if(context, flow_node) elif flow_scope.type in ('try_stmt', 'while_stmt'): return UNSURE # Only reachable branches need to be examined further. if reachable in (UNREACHABLE, UNSURE): return reachable if value_scope != flow_scope and value_scope != flow_scope.parent: flow_scope = get_parent_scope(flow_scope, include_flows=True) return reachable & _break_check(context, value_scope, flow_scope, node) else: return reachable
def from_scope_node(scope_node, is_nested=True): if scope_node == self.tree_node: return self if scope_node.type in ('funcdef', 'lambdef', 'classdef'): return self.create_value(scope_node).as_context() elif scope_node.type in ('comp_for', 'sync_comp_for'): parent_scope = parser_utils.get_parent_scope(scope_node) parent_context = from_scope_node(parent_scope) if node.start_pos >= scope_node.children[-1].start_pos: return parent_context return CompForContext(parent_context, scope_node) raise Exception("There's a scope that was not managed: %s" % scope_node)
def _search_function_arguments(module_context, funcdef, string_name): """ Returns a list of param names. """ compare_node = funcdef if string_name == '__init__': cls = get_parent_scope(funcdef) if cls.type == 'classdef': string_name = cls.name.value compare_node = cls found_arguments = False i = 0 inference_state = module_context.inference_state if settings.dynamic_params_for_other_modules: module_contexts = get_module_contexts_containing_name( inference_state, [module_context], string_name, # Limit the amounts of files to be opened massively. limit_reduction=5, ) else: module_contexts = [module_context] for for_mod_context in module_contexts: for name, trailer in _get_potential_nodes(for_mod_context, string_name): i += 1 # This is a simple way to stop Medi's dynamic param recursion # from going wild: The deeper Medi's in the recursion, the less # code should be inferred. if i * inference_state.dynamic_params_depth > MAX_PARAM_SEARCHES: return random_context = for_mod_context.create_context(name) for arguments in _check_name_for_execution(inference_state, random_context, compare_node, name, trailer): found_arguments = True yield arguments # If there are results after processing a module, we're probably # good to process. This is a speed optimization. if found_arguments: return
def _check_for_additional_knowledge(self, name_or_str, name_context, position): name_context = name_context or self # Add isinstance and other if/assert knowledge. if isinstance(name_or_str, Name) and not name_context.is_instance(): flow_scope = name_or_str base_nodes = [name_context.tree_node] if any(b.type in ('comp_for', 'sync_comp_for') for b in base_nodes): return NO_VALUES from medi.inference.finder import check_flow_information while True: flow_scope = get_parent_scope(flow_scope, include_flows=True) n = check_flow_information(name_context, flow_scope, name_or_str, position) if n is not None: return n if flow_scope in base_nodes: break return NO_VALUES
def reachability_check(context, value_scope, node, origin_scope=None): if is_big_annoying_library(context) \ or not context.inference_state.flow_analysis_enabled: return UNSURE first_flow_scope = get_parent_scope(node, include_flows=True) if origin_scope is not None: origin_flow_scopes = list(_get_flow_scopes(origin_scope)) node_flow_scopes = list(_get_flow_scopes(node)) branch_matches = True for flow_scope in origin_flow_scopes: if flow_scope in node_flow_scopes: node_keyword = get_flow_branch_keyword(flow_scope, node) origin_keyword = get_flow_branch_keyword(flow_scope, origin_scope) branch_matches = node_keyword == origin_keyword if flow_scope.type == 'if_stmt': if not branch_matches: return UNREACHABLE elif flow_scope.type == 'try_stmt': if not branch_matches and origin_keyword == 'else' \ and node_keyword == 'except': return UNREACHABLE if branch_matches: break # Direct parents get resolved, we filter scopes that are separate # branches. This makes sense for autocompletion and static analysis. # For actual Python it doesn't matter, because we're talking about # potentially unreachable code. # e.g. `if 0:` would cause all name lookup within the flow make # unaccessible. This is not a "problem" in Python, because the code is # never called. In Medi though, we still want to infer types. while origin_scope is not None: if first_flow_scope == origin_scope and branch_matches: return REACHABLE origin_scope = origin_scope.parent return _break_check(context, value_scope, first_flow_scope, node)
def tree_name_to_values(inference_state, context, tree_name): value_set = NO_VALUES module_node = context.get_root_context().tree_node # First check for annotations, like: `foo: int = 3` if module_node is not None: names = module_node.get_used_names().get(tree_name.value, []) found_annotation = False for name in names: expr_stmt = name.parent if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign": correct_scope = parser_utils.get_parent_scope(name) == context.tree_node if correct_scope: found_annotation = True value_set |= annotation.infer_annotation( context, expr_stmt.children[1].children[1] ).execute_annotation() if found_annotation: return value_set types = [] node = tree_name.get_definition(import_name_always=True, include_setitem=True) if node is None: node = tree_name.parent if node.type == 'global_stmt': c = context.create_context(tree_name) if c.is_module(): # In case we are already part of the module, there is no point # in looking up the global statement anymore, because it's not # valid at that point anyway. return NO_VALUES # For global_stmt lookups, we only need the first possible scope, # which means the function itself. filter = next(c.get_filters()) names = filter.get(tree_name.value) return ValueSet.from_sets(name.infer() for name in names) elif node.type not in ('import_from', 'import_name'): c = context.create_context(tree_name) return infer_atom(c, tree_name) typ = node.type if typ == 'for_stmt': types = annotation.find_type_from_comment_hint_for(context, node, tree_name) if types: return types if typ == 'with_stmt': types = annotation.find_type_from_comment_hint_with(context, node, tree_name) if types: return types if typ in ('for_stmt', 'comp_for', 'sync_comp_for'): try: types = context.predefined_names[node][tree_name.value] except KeyError: cn = ContextualizedNode(context, node.children[3]) for_types = iterate_values( cn.infer(), contextualized_node=cn, is_async=node.parent.type == 'async_stmt', ) n = TreeNameDefinition(context, tree_name) types = check_tuple_assignments(n, for_types) elif typ == 'expr_stmt': types = infer_expr_stmt(context, node, tree_name) elif typ == 'with_stmt': value_managers = context.infer_node(node.get_test_node_from_name(tree_name)) enter_methods = value_managers.py__getattribute__(u'__enter__') return enter_methods.execute_with_values() elif typ in ('import_from', 'import_name'): types = imports.infer_import(context, tree_name) elif typ in ('funcdef', 'classdef'): types = _apply_decorators(context, node) elif typ == 'try_stmt': # TODO an exception can also be a tuple. Check for those. # TODO check for types that are not classes and add it to # the static analysis report. exceptions = context.infer_node(tree_name.get_previous_sibling().get_previous_sibling()) types = exceptions.execute_with_values() elif typ == 'param': types = NO_VALUES elif typ == 'del_stmt': types = NO_VALUES else: raise ValueError("Should not happen. type: %s" % typ) return types
def _get_flow_scopes(node): while True: node = get_parent_scope(node, include_flows=True) if node is None or is_scope(node): return yield node
def is_module_scope_name(name): parent_scope = get_parent_scope(name) # async functions have an extra wrapper. Strip it. if parent_scope and parent_scope.type == 'async_stmt': parent_scope = parent_scope.parent return parent_scope in (module, None)