def _remove_statements(evaluator, stmt, name): """ This is the part where statements are being stripped. Due to lazy evaluation, statements like a = func; b = a; b() have to be evaluated. """ types = [] # Remove the statement docstr stuff for now, that has to be # implemented with the evaluator class. #if stmt.docstr: #res_new.append(stmt) check_instance = None if isinstance(stmt, er.InstanceElement) and stmt.is_class_var: check_instance = stmt.instance stmt = stmt.var types += evaluator.eval_statement(stmt, seek_name=name) if check_instance is not None: # class renames types = [er.get_instance_el(evaluator, check_instance, a, True) if isinstance(a, (er.Function, pr.Function)) else a for a in types] return types
def _remove_statements(evaluator, stmt, name): """ This is the part where statements are being stripped. Due to lazy evaluation, statements like a = func; b = a; b() have to be evaluated. """ types = [] # Remove the statement docstr stuff for now, that has to be # implemented with the evaluator class. #if stmt.docstr: #res_new.append(stmt) check_instance = None if isinstance(stmt, er.InstanceElement) and stmt.is_class_var: check_instance = stmt.instance stmt = stmt.var types += evaluator.eval_statement(stmt, seek_name=name) if check_instance is not None: # class renames types = [ er.get_instance_el(evaluator, check_instance, a, True) if isinstance(a, (er.Function, pr.Function)) else a for a in types ] return types
def scope_names_generator(self, position=None): """ It returns e.g. for a list: append, pop, ... """ # `array.type` is a string with the type, e.g. 'list'. scope = self._evaluator.find_types(compiled.builtin, self._array.type)[0] scope = self._evaluator.execute(scope)[0] # builtins only have one class from jedi.evaluate.representation import get_instance_el for _, names in scope.scope_names_generator(): yield self, [get_instance_el(self._evaluator, self, n) for n in names]
def _remove_statements(self, stmt, name): """ This is the part where statements are being stripped. Due to lazy evaluation, statements like a = func; b = a; b() have to be evaluated. """ evaluator = self._evaluator types = [] # Remove the statement docstr stuff for now, that has to be # implemented with the evaluator class. #if stmt.docstr: #res_new.append(stmt) check_instance = None if isinstance(stmt, er.InstanceElement) and stmt.is_class_var: check_instance = stmt.instance stmt = stmt.var types += evaluator.eval_statement(stmt, seek_name=unicode(self.name_str)) # check for `except X as y` usages, because y needs to be instantiated. p = stmt.parent # TODO this looks really hacky, improve parser representation! if isinstance(p, pr.Flow) and p.command == 'except' and p.inputs: as_names = p.inputs[0].as_names try: if as_names[0] == name: # TODO check for types that are not classes and add it to # the static analysis report. types = list(chain.from_iterable( evaluator.execute(t) for t in types)) except IndexError: pass if check_instance is not None: # class renames types = [er.get_instance_el(evaluator, check_instance, a, True) if isinstance(a, (er.Function, pr.Function)) else a for a in types] return types
def _remove_statements(evaluator, context, stmt, name): """ This is the part where statements are being stripped. Due to lazy evaluation, statements like a = func; b = a; b() have to be evaluated. """ types = set() check_instance = None pep0484types = \ pep0484.find_type_from_comment_hint_assign(context, stmt, name) if pep0484types: return pep0484types types |= context.eval_stmt(stmt, seek_name=name) if check_instance is not None: # class renames types = set([er.get_instance_el(evaluator, check_instance, a, True) if isinstance(a, er.Function) else a for a in types]) return types
def _remove_statements(evaluator, context, stmt, name): """ This is the part where statements are being stripped. Due to lazy evaluation, statements like a = func; b = a; b() have to be evaluated. """ types = set() check_instance = None pep0484types = \ pep0484.find_type_from_comment_hint_assign(context, stmt, name) if pep0484types: return pep0484types types |= context.eval_stmt(stmt, seek_name=name) if check_instance is not None: # class renames types = set([ er.get_instance_el(evaluator, check_instance, a, True) if isinstance(a, er.Function) else a for a in types ]) return types
def _check_array_additions(evaluator, compare_array, module, is_list): """ Checks if a `Array` has "add" (append, insert, extend) statements: >>> a = [""] >>> a.append(1) """ debug.dbg('Dynamic array search for %s' % compare_array, color='MAGENTA') if not settings.dynamic_array_additions or isinstance( module, compiled.CompiledObject): debug.dbg('Dynamic array search aborted.', color='MAGENTA') return set() def check_additions(arglist, add_name): params = list(param.Arguments(evaluator, arglist).unpack()) result = set() if add_name in ['insert']: params = params[1:] if add_name in ['append', 'add', 'insert']: for key, nodes in params: result |= unite(evaluator.eval_element(node) for node in nodes) elif add_name in ['extend', 'update']: for key, nodes in params: for node in nodes: types = evaluator.eval_element(node) result |= py__iter__types(evaluator, types, node) return result from jedi.evaluate import representation as er, param def get_execution_parent(element): """ Used to get an Instance/FunctionExecution parent """ if isinstance(element, Array): node = element.atom else: # Is an Instance with an # Arguments([AlreadyEvaluated([_ArrayInstance])]) inside # Yeah... I know... It's complicated ;-) node = list(element.var_args.argument_node[0])[0].var_args.trailer if isinstance(node, er.InstanceElement) or node is None: return node return node.get_parent_until(er.FunctionExecution) temp_param_add, settings.dynamic_params_for_other_modules = \ settings.dynamic_params_for_other_modules, False search_names = ['append', 'extend', 'insert' ] if is_list else ['add', 'update'] comp_arr_parent = get_execution_parent(compare_array) added_types = set() for add_name in search_names: try: possible_names = module.used_names[add_name] except KeyError: continue else: for name in possible_names: # Check if the original scope is an execution. If it is, one # can search for the same statement, that is in the module # dict. Executions are somewhat special in jedi, since they # literally copy the contents of a function. if isinstance(comp_arr_parent, er.FunctionExecution): if comp_arr_parent.start_pos < name.start_pos < comp_arr_parent.end_pos: name = comp_arr_parent.name_for_position( name.start_pos) else: # Don't check definitions that are not defined in the # same function. This is not "proper" anyway. It also # improves Jedi's speed for array lookups, since we # don't have to check the whole source tree anymore. continue trailer = name.parent power = trailer.parent trailer_pos = power.children.index(trailer) try: execution_trailer = power.children[trailer_pos + 1] except IndexError: continue else: if execution_trailer.type != 'trailer' \ or execution_trailer.children[0] != '(' \ or execution_trailer.children[1] == ')': continue power = helpers.call_of_leaf(name, cut_own_trailer=True) # InstanceElements are special, because they don't get copied, # but have this wrapper around them. if isinstance(comp_arr_parent, er.InstanceElement): power = er.get_instance_el(evaluator, comp_arr_parent.instance, power) if evaluator.recursion_detector.push_stmt(power): # Check for recursion. Possible by using 'extend' in # combination with function calls. continue try: if compare_array in evaluator.eval_element(power): # The arrays match. Now add the results added_types |= check_additions( execution_trailer.children[1], add_name) finally: evaluator.recursion_detector.pop_stmt() # reset settings settings.dynamic_params_for_other_modules = temp_param_add debug.dbg('Dynamic array result %s' % added_types, color='MAGENTA') return added_types
def _check_array_additions(evaluator, compare_array, module, is_list): """ Checks if a `Array` has "add" (append, insert, extend) statements: >>> a = [""] >>> a.append(1) """ if not settings.dynamic_array_additions or isinstance(module, compiled.CompiledObject): return [] def check_additions(arglist, add_name): params = list(param.Arguments(evaluator, arglist).unpack()) result = [] if add_name in ['insert']: params = params[1:] if add_name in ['append', 'add', 'insert']: for key, nodes in params: result += unite(evaluator.eval_element(node) for node in nodes) elif add_name in ['extend', 'update']: for key, nodes in params: iterators = unite(evaluator.eval_element(node) for node in nodes) result += get_iterator_types(iterators) return result from jedi.evaluate import representation as er, param def get_execution_parent(element): """ Used to get an Instance/FunctionExecution parent """ if isinstance(element, Array): node = element.atom else: # Is an Instance with an # Arguments([AlreadyEvaluated([ArrayInstance])]) inside # Yeah... I know... It's complicated ;-) node = list(element.var_args.argument_node[0])[0].var_args.trailer if isinstance(node, er.InstanceElement): return node return node.get_parent_until(er.FunctionExecution) temp_param_add, settings.dynamic_params_for_other_modules = \ settings.dynamic_params_for_other_modules, False search_names = ['append', 'extend', 'insert'] if is_list else ['add', 'update'] comp_arr_parent = get_execution_parent(compare_array) added_types = [] for add_name in search_names: try: possible_names = module.used_names[add_name] except KeyError: continue else: for name in possible_names: # Check if the original scope is an execution. If it is, one # can search for the same statement, that is in the module # dict. Executions are somewhat special in jedi, since they # literally copy the contents of a function. if isinstance(comp_arr_parent, er.FunctionExecution): if comp_arr_parent.start_pos < name.start_pos < comp_arr_parent.end_pos: name = comp_arr_parent.name_for_position(name.start_pos) else: # Don't check definitions that are not defined in the # same function. This is not "proper" anyway. It also # improves Jedi's speed for array lookups, since we # don't have to check the whole source tree anymore. continue trailer = name.parent power = trailer.parent trailer_pos = power.children.index(trailer) try: execution_trailer = power.children[trailer_pos + 1] except IndexError: continue else: if execution_trailer.type != 'trailer' \ or execution_trailer.children[0] != '(' \ or execution_trailer.children[1] == ')': continue power = helpers.call_of_name(name, cut_own_trailer=True) # InstanceElements are special, because they don't get copied, # but have this wrapper around them. if isinstance(comp_arr_parent, er.InstanceElement): power = er.get_instance_el(evaluator, comp_arr_parent.instance, power) if evaluator.recursion_detector.push_stmt(power): # Check for recursion. Possible by using 'extend' in # combination with function calls. continue if compare_array in evaluator.eval_element(power): # The arrays match. Now add the results added_types += check_additions(execution_trailer.children[1], add_name) evaluator.recursion_detector.pop_stmt() # reset settings settings.dynamic_params_for_other_modules = temp_param_add return added_types
def _check_array_additions(evaluator, compare_array, module, is_list): """ Checks if a `pr.Array` has "add" statements: >>> a = [""] >>> a.append(1) """ if not settings.dynamic_array_additions or isinstance(module, compiled.CompiledObject): return [] def check_calls(calls, add_name): """ Calls are processed here. The part before the call is searched and compared with the original Array. """ result = [] for c in calls: call_path = list(c.generate_call_path()) call_path_simple = [unicode(n) if isinstance(n, pr.Name) else n for n in call_path] separate_index = call_path_simple.index(add_name) if add_name == call_path_simple[-1] or separate_index == 0: # this means that there is no execution -> [].append # or the keyword is at the start -> append() continue backtrack_path = iter(call_path[:separate_index]) position = c.start_pos scope = c.get_parent_scope() found = evaluator.eval_call_path(backtrack_path, scope, position) if not compare_array in found: continue params = call_path[separate_index + 1] if not params.values: continue # no params: just ignore it if add_name in ['append', 'add']: for param in params: result += evaluator.eval_statement(param) elif add_name in ['insert']: try: second_param = params[1] except IndexError: continue else: result += evaluator.eval_statement(second_param) elif add_name in ['extend', 'update']: for param in params: iterators = evaluator.eval_statement(param) result += get_iterator_types(iterators) return result from jedi.evaluate import representation as er def get_execution_parent(element, *stop_classes): """ Used to get an Instance/FunctionExecution parent """ if isinstance(element, Array): stmt = element._array.parent else: # is an Instance with an ArrayInstance inside stmt = element.var_args[0].var_args.parent if isinstance(stmt, er.InstanceElement): stop_classes = list(stop_classes) + [er.Function] return stmt.get_parent_until(stop_classes) temp_param_add = settings.dynamic_params_for_other_modules settings.dynamic_params_for_other_modules = False search_names = ['append', 'extend', 'insert'] if is_list else \ ['add', 'update'] comp_arr_parent = get_execution_parent(compare_array, er.FunctionExecution) possible_stmts = [] res = [] for n in search_names: try: possible_stmts += module.used_names[n] except KeyError: continue for stmt in possible_stmts: # Check if the original scope is an execution. If it is, one # can search for the same statement, that is in the module # dict. Executions are somewhat special in jedi, since they # literally copy the contents of a function. if isinstance(comp_arr_parent, er.FunctionExecution): stmt = comp_arr_parent. \ get_statement_for_position(stmt.start_pos) if stmt is None: continue # InstanceElements are special, because they don't get copied, # but have this wrapper around them. if isinstance(comp_arr_parent, er.InstanceElement): stmt = er.get_instance_el(comp_arr_parent.instance, stmt) if evaluator.recursion_detector.push_stmt(stmt): # check recursion continue res += check_calls(helpers.scan_statement_for_calls(stmt, n), n) evaluator.recursion_detector.pop_stmt() # reset settings settings.dynamic_params_for_other_modules = temp_param_add return res