diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index 440727c6..ddc8ee25 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -84,6 +84,7 @@ from jedi.evaluate import recursion from jedi.evaluate.cache import memoize_default from jedi import docstrings from jedi.evaluate import dynamic +from jedi.evaluate import stdlib def get_defined_names_for_position(scope, position=None, start_scope=None): @@ -106,7 +107,7 @@ def get_defined_names_for_position(scope, position=None, start_scope=None): # because class variables are always valid and the `self.` variables, too. if (not position or isinstance(scope, (er.Array, er.Instance)) or start_scope != scope - and isinstance(start_scope, (pr.Function, er.Execution))): + and isinstance(start_scope, (pr.Function, er.FunctionExecution))): return names names_new = [] for n in names: @@ -193,7 +194,7 @@ class Evaluator(object): scope = scope.parent # This is used, because subscopes (Flow scopes) would distort the # results. - if scope and scope.isinstance(er.Function, pr.Function, er.Execution): + if scope and scope.isinstance(er.Function, pr.Function, er.FunctionExecution): in_func_scope = scope # Add star imports. @@ -668,6 +669,11 @@ class Evaluator(object): if obj.isinstance(er.Function): obj = obj.get_decorated_func() + try: + return stdlib.execute(self, obj, params) + except stdlib.NotInStdLib: + pass + if obj.isinstance(er.Class): # There maybe executions of executions. return [er.Instance(self, obj, params)] @@ -686,7 +692,7 @@ class Evaluator(object): else: debug.warning("no execution possible", obj) else: - stmts = er.Execution(self, obj, params).get_return_types(evaluate_generator) + stmts = er.FunctionExecution(self, obj, params).get_return_types(evaluate_generator) debug.dbg('execute: %s in %s' % (stmts, self)) return imports.strip_imports(self, stmts) diff --git a/jedi/evaluate/dynamic.py b/jedi/evaluate/dynamic.py index f6b1b323..eaf5037e 100644 --- a/jedi/evaluate/dynamic.py +++ b/jedi/evaluate/dynamic.py @@ -335,7 +335,7 @@ def _check_array_additions(evaluator, compare_array, module, is_list): from jedi import evaluate def get_execution_parent(element, *stop_classes): - """ Used to get an Instance/Execution parent """ + """ Used to get an Instance/FunctionExecution parent """ if isinstance(element, er.Array): stmt = element._array.parent else: @@ -350,7 +350,7 @@ def _check_array_additions(evaluator, compare_array, module, is_list): search_names = ['append', 'extend', 'insert'] if is_list else \ ['add', 'update'] - comp_arr_parent = get_execution_parent(compare_array, er.Execution) + comp_arr_parent = get_execution_parent(compare_array, er.FunctionExecution) possible_stmts = [] res = [] @@ -364,7 +364,7 @@ def _check_array_additions(evaluator, compare_array, module, is_list): # 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.Execution): + if isinstance(comp_arr_parent, er.FunctionExecution): stmt = comp_arr_parent. \ get_statement_for_position(stmt.start_pos) if stmt is None: diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index e815bd00..4536e796 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -67,7 +67,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)): @memoize_default(None) def _get_method_execution(self, func): func = InstanceElement(self._evaluator, self, func, True) - return Execution(self._evaluator, func, self.var_args) + return FunctionExecution(self._evaluator, func, self.var_args) def _get_func_self_name(self, func): """ @@ -392,7 +392,7 @@ class Function(use_metaclass(CachedMetaClass, pr.IsScope)): return "" % (type(self).__name__, self.base_func, dec) -class Execution(Executable): +class FunctionExecution(Executable): """ This class is used to evaluate functions and their returns. @@ -401,69 +401,10 @@ class Execution(Executable): multiple calls to functions and recursion has to be avoided. But this is responsibility of the decorators. """ - def _follow_var_arg(self, index): - try: - stmt = self.var_args[index] - except IndexError: - return [] - else: - if isinstance(stmt, pr.Statement): - return self._evaluator.eval_statement(stmt) - else: - return [stmt] # just some arbitrary object - @memoize_default(default=()) @recursion.execution_recursion_decorator def get_return_types(self, evaluate_generator=False): - """ Get the return types of a function. """ - base = self.base - stmts = [] - if base.parent == builtin.Builtin.scope \ - and not isinstance(base, (Generator, Array)): - func_name = str(base.name) - - # some implementations of builtins: - if func_name == 'getattr': - # follow the first param - objects = self._follow_var_arg(0) - names = self._follow_var_arg(1) - for obj in objects: - if not isinstance(obj, (Instance, Class, pr.Module)): - debug.warning('getattr called without instance') - continue - - for arr_name in names: - if not isinstance(arr_name, Instance): - debug.warning('getattr called without str') - continue - if len(arr_name.var_args) != 1: - debug.warning('jedi getattr is too simple') - key = arr_name.var_args[0] - stmts += self._evaluator.follow_path(iter([key]), [obj], base) - return stmts - elif func_name == 'type': - # otherwise it would be a metaclass - if len(self.var_args) == 1: - objects = self._follow_var_arg(0) - return [o.base for o in objects if isinstance(o, Instance)] - elif func_name == 'super': - # TODO make this able to detect multiple inheritance super - accept = (pr.Function,) - func = self.var_args.get_parent_until(accept) - if func.isinstance(*accept): - cls = func.get_parent_until(accept + (pr.Class,), - include_current=False) - if isinstance(cls, pr.Class): - cls = Class(self._evaluator, cls) - su = cls.get_super_classes() - if su: - return [Instance(self._evaluator, su[0])] - return [] - - return self._get_function_returns(base, evaluate_generator) - - def _get_function_returns(self, func, evaluate_generator): - """ A normal Function execution """ + func = self.base # Feed the listeners, with the params. for listener in func.listeners: listener.execute(self._get_params()) @@ -479,7 +420,7 @@ class Execution(Executable): @memoize_default(default=()) def _get_params(self): """ - This returns the params for an Execution/Instance and is injected as a + This returns the params for an TODO and is injected as a 'hack' into the pr.Function class. This needs to be here, because Instance can have __init__ functions, which act the same way as normal functions. diff --git a/jedi/evaluate/stdlib.py b/jedi/evaluate/stdlib.py new file mode 100644 index 00000000..bbb3572b --- /dev/null +++ b/jedi/evaluate/stdlib.py @@ -0,0 +1,90 @@ +""" +Implementations of standard library functions, because it's not possible to +understand them with Jedi. +""" + +from jedi.evaluate import builtin +from jedi.evaluate import representation as er +from jedi.parser import representation as pr +from jedi import debug + + +class NotInStdLib(LookupError): + pass + + +def execute(evaluator, obj, params): + if not isinstance(obj, (er.Generator, er.Array)): + obj_name = str(obj.name) + if obj.parent == builtin.Builtin.scope: + # for now we just support builtin functions. + try: + return _implemented['builtins'][obj_name](evaluator, obj, params) + except KeyError: + pass + raise NotInStdLib() + + +def _follow_param(evaluator, params, index): + try: + stmt = params[index] + except IndexError: + return [] + else: + if isinstance(stmt, pr.Statement): + return evaluator.eval_statement(stmt) + else: + return [stmt] # just some arbitrary object + + +def builtins_getattr(evaluator, obj, params): + stmts = [] + # follow the first param + objects = _follow_param(evaluator, params, 0) + names = _follow_param(evaluator, params, 1) + for obj in objects: + if not isinstance(obj, (er.Instance, er.Class, pr.Module)): + debug.warning('getattr called without instance') + continue + + for arr_name in names: + if not isinstance(arr_name, er.Instance): + debug.warning('getattr called without str') + continue + if len(arr_name.var_args) != 1: + debug.warning('jedi getattr is too simple') + key = arr_name.var_args[0] + stmts += evaluator.follow_path(iter([key]), [obj], obj) + return stmts + + +def builtins_type(evaluator, obj, params): + if len(params) == 1: + # otherwise it would be a metaclass... maybe someday... + objects = _follow_param(evaluator, params, 0) + return [o.base for o in objects if isinstance(o, er.Instance)] + return [] + + +def builtins_super(evaluator, obj, params): + # TODO make this able to detect multiple inheritance super + accept = (pr.Function,) + func = params.get_parent_until(accept) + if func.isinstance(*accept): + cls = func.get_parent_until(accept + (pr.Class,), + include_current=False) + if isinstance(cls, pr.Class): + cls = er.Class(evaluator, cls) + su = cls.get_super_classes() + if su: + return [er.Instance(evaluator, su[0])] + return [] + + +_implemented = { + 'builtins': { + 'getattr': builtins_getattr, + 'type': builtins_type, + 'super': builtins_super, + } +}