1
0
forked from VimPlug/jedi

Merge remote-tracking branch 'upstream/dev' into fix_runtime_error

Conflicts:
	jedi/evaluate/imports.py
This commit is contained in:
ColinDuquesnoy
2014-09-29 11:49:21 +02:00
25 changed files with 519 additions and 413 deletions
+14 -26
View File
@@ -167,8 +167,8 @@ class Script(object):
# Allow access on _definition here, because it's a # Allow access on _definition here, because it's a
# public API and we don't want to make the internal # public API and we don't want to make the internal
# Name object public. # Name object public.
if p._name.get_definition().stars == 0: # no *args/**kwargs if p._definition.stars == 0: # no *args/**kwargs
completions.append((p._name.parent, p)) completions.append((p._name, p._name))
if not path and not isinstance(user_stmt, pr.Import): if not path and not isinstance(user_stmt, pr.Import):
# add keywords # add keywords
@@ -179,17 +179,15 @@ class Script(object):
comps = [] comps = []
comp_dct = {} comp_dct = {}
for c, s in set(completions): for c, s in set(completions):
# TODO Remove this line. c should be a namepart even before that.
c = c.names[-1]
n = str(c) n = str(c)
if settings.case_insensitive_completion \ if settings.case_insensitive_completion \
and n.lower().startswith(like.lower()) \ and n.lower().startswith(like.lower()) \
or n.startswith(like): or n.startswith(like):
if not filter_private_variable(s, user_stmt or self._parser.user_scope(), n): if not filter_private_variable(s, user_stmt or self._parser.user_scope(), n):
if isinstance(c.parent.parent, (pr.Function, pr.Class)): if isinstance(c.parent, (pr.Function, pr.Class)):
# TODO I think this is a hack. It should be an # TODO I think this is a hack. It should be an
# er.Function/er.Class before that. # er.Function/er.Class before that.
c = er.wrap(self._evaluator, c.parent.parent).name.names[-1] c = er.wrap(self._evaluator, c.parent).name
new = classes.Completion(self._evaluator, c, needs_dot, len(like), s) new = classes.Completion(self._evaluator, c, needs_dot, len(like), s)
k = (new.name, new.complete) # key k = (new.name, new.complete) # key
if k in comp_dct and settings.no_completion_duplicates: if k in comp_dct and settings.no_completion_duplicates:
@@ -395,10 +393,9 @@ class Script(object):
definitions = set(self._prepare_goto(goto_path)) definitions = set(self._prepare_goto(goto_path))
definitions = resolve_import_paths(definitions) definitions = resolve_import_paths(definitions)
names = [s if isinstance(s, pr.Name) else s.name for s in definitions names = [s.name for s in definitions
if s is not imports.ImportWrapper.GlobalNamespace] if s is not imports.ImportWrapper.GlobalNamespace]
defs = [classes.Definition(self._evaluator, name.names[-1]) defs = [classes.Definition(self._evaluator, name) for name in names]
for name in names]
return helpers.sorted_definitions(set(defs)) return helpers.sorted_definitions(set(defs))
def goto_assignments(self): def goto_assignments(self):
@@ -432,7 +429,7 @@ class Script(object):
and d.start_pos == (0, 0): and d.start_pos == (0, 0):
i = imports.ImportWrapper(self._evaluator, d.parent).follow(is_goto=True) i = imports.ImportWrapper(self._evaluator, d.parent).follow(is_goto=True)
definitions.remove(d) definitions.remove(d)
definitions |= follow_inexistent_imports(i.names[-1]) definitions |= follow_inexistent_imports(i)
return definitions return definitions
goto_path = self._user_context.get_path_under_cursor() goto_path = self._user_context.get_path_under_cursor()
@@ -455,7 +452,7 @@ class Script(object):
if next(context) in ('class', 'def'): if next(context) in ('class', 'def'):
# The cursor is on a class/function name. # The cursor is on a class/function name.
user_scope = self._parser.user_scope() user_scope = self._parser.user_scope()
definitions = set([user_scope.name.names[-1]]) definitions = set([user_scope.name])
elif isinstance(user_stmt, pr.Import): elif isinstance(user_stmt, pr.Import):
s, name_part = helpers.get_on_import_stmt(self._evaluator, s, name_part = helpers.get_on_import_stmt(self._evaluator,
self._user_context, user_stmt) self._user_context, user_stmt)
@@ -467,7 +464,7 @@ class Script(object):
if add_import_name: if add_import_name:
import_name = user_stmt.get_defined_names() import_name = user_stmt.get_defined_names()
# imports have only one name # imports have only one name
np = import_name[0].names[-1] np = import_name[0]
if not user_stmt.star and unicode(name_part) == unicode(np): if not user_stmt.star and unicode(name_part) == unicode(np):
definitions.append(np) definitions.append(np)
else: else:
@@ -477,8 +474,9 @@ class Script(object):
if isinstance(user_stmt, pr.ExprStmt): if isinstance(user_stmt, pr.ExprStmt):
for name in user_stmt.get_defined_names(): for name in user_stmt.get_defined_names():
if name.start_pos <= self._pos <= name.end_pos \ if name.start_pos <= self._pos <= name.end_pos \
and len(name.names) == 1: and (not isinstance(name.parent, pr.Call)
return [name.names[0]] or name.parent.next is None):
return [name]
defs = self._evaluator.goto(stmt, call_path) defs = self._evaluator.goto(stmt, call_path)
definitions = follow_inexistent_imports(defs) definitions = follow_inexistent_imports(defs)
@@ -504,16 +502,6 @@ class Script(object):
# Without a definition for a name we cannot find references. # Without a definition for a name we cannot find references.
return [] return []
# Once Script._goto works correct, we can probably remove this
# branch.
if isinstance(user_stmt, pr.ExprStmt):
c = user_stmt.expression_list()[0]
if not isinstance(c, unicode) and self._pos < c.start_pos:
# The lookup might be before `=`
definitions = [v.names[-1] for v in user_stmt.get_defined_names()
if unicode(v.names[-1]) ==
list(definitions)[0].get_code()]
if not isinstance(user_stmt, pr.Import): if not isinstance(user_stmt, pr.Import):
# import case is looked at with add_import_name option # import case is looked at with add_import_name option
definitions = usages.usages_add_import_modules(self._evaluator, definitions = usages.usages_add_import_modules(self._evaluator,
@@ -572,7 +560,7 @@ class Script(object):
key_name = unicode(detail[0][0].name) key_name = unicode(detail[0][0].name)
except (IndexError, AttributeError): except (IndexError, AttributeError):
pass pass
return [classes.CallSignature(self._evaluator, o.name.names[-1], call, index, key_name) return [classes.CallSignature(self._evaluator, o.name, call, index, key_name)
for o in origins if hasattr(o, 'py__call__')] for o in origins if hasattr(o, 'py__call__')]
def _analysis(self): def _analysis(self):
@@ -583,7 +571,7 @@ class Script(object):
iw = imports.ImportWrapper(self._evaluator, i, iw = imports.ImportWrapper(self._evaluator, i,
nested_resolve=True).follow() nested_resolve=True).follow()
if i.is_nested() and any(not isinstance(i, pr.Module) for i in iw): if i.is_nested() and any(not isinstance(i, pr.Module) for i in iw):
analysis.add(self._evaluator, 'import-error', i.namespace.names[-1]) analysis.add(self._evaluator, 'import-error', i.namespace_names[-1])
for stmt in sorted(stmts, key=lambda obj: obj.start_pos): for stmt in sorted(stmts, key=lambda obj: obj.start_pos):
if not (isinstance(stmt.parent, pr.ForFlow) if not (isinstance(stmt.parent, pr.ForFlow)
and stmt.parent.set_stmt == stmt): and stmt.parent.set_stmt == stmt):
+6 -12
View File
@@ -36,8 +36,7 @@ def defined_names(evaluator, scope):
pair = next(get_names_of_scope(evaluator, scope, star_search=False, pair = next(get_names_of_scope(evaluator, scope, star_search=False,
include_builtin=False), None) include_builtin=False), None)
names = pair[1] if pair else [] names = pair[1] if pair else []
names = [n for n in names if isinstance(n, pr.Import) or (len(n) == 1)] return [Definition(evaluator, d) for d in sorted(names, key=lambda s: s.start_pos)]
return [Definition(evaluator, d.names[-1]) for d in sorted(names, key=lambda s: s.start_pos)]
class BaseDefinition(object): class BaseDefinition(object):
@@ -168,16 +167,11 @@ class BaseDefinition(object):
def _path(self): def _path(self):
"""The module path.""" """The module path."""
path = [] path = []
def insert_nonnone(x):
if x:
path.insert(0, x)
par = self._definition par = self._definition
while par is not None: while par is not None:
if isinstance(par, pr.Import): if isinstance(par, pr.Import):
insert_nonnone(par.namespace) path += par.from_names
insert_nonnone(par.from_ns) path += par.namespace_names
if par.relative_count == 0: if par.relative_count == 0:
break break
with common.ignored(AttributeError): with common.ignored(AttributeError):
@@ -376,12 +370,12 @@ class BaseDefinition(object):
params = sub.params[1:] # ignore self params = sub.params[1:] # ignore self
except KeyError: except KeyError:
return [] return []
return [_Param(self._evaluator, p.get_name().names[-1]) for p in params] return [_Param(self._evaluator, p.get_name()) for p in params]
def parent(self): def parent(self):
scope = self._definition.get_parent_scope() scope = self._definition.get_parent_scope()
non_flow = scope.get_parent_until(pr.Flow, reverse=True) non_flow = scope.get_parent_until(pr.Flow, reverse=True)
return Definition(self._evaluator, non_flow.name.names[-1]) return Definition(self._evaluator, non_flow.name)
def __repr__(self): def __repr__(self):
return "<%s %s>" % (type(self).__name__, self.description) return "<%s %s>" % (type(self).__name__, self.description)
@@ -539,7 +533,7 @@ class Completion(BaseDefinition):
it's just PITA-slow. it's just PITA-slow.
""" """
defs = self._follow_statements_imports() defs = self._follow_statements_imports()
return [Definition(self._evaluator, d.name.names[-1]) for d in defs] return [Definition(self._evaluator, d.name) for d in defs]
class Definition(use_metaclass(CachedMetaClass, BaseDefinition)): class Definition(use_metaclass(CachedMetaClass, BaseDefinition)):
+7 -7
View File
@@ -28,14 +28,14 @@ def get_on_import_stmt(evaluator, user_context, user_stmt, is_like_search=False)
import_names = user_stmt.get_all_import_names() import_names = user_stmt.get_all_import_names()
kill_count = -1 kill_count = -1
cur_name_part = None cur_name_part = None
for i in import_names: for name in import_names:
if user_stmt.alias == i: if user_stmt.alias == name:
continue continue
for name_part in i.names:
if name_part.end_pos >= user_context.position: if name.end_pos >= user_context.position:
if not cur_name_part: if not cur_name_part:
cur_name_part = name_part cur_name_part = name
kill_count += 1 kill_count += 1
context = user_context.get_context() context = user_context.get_context()
just_from = next(context) == 'from' just_from = next(context) == 'from'
+2 -4
View File
@@ -48,8 +48,7 @@ class LazyName(helpers.FakeName):
module = obj module = obj
else: else:
class FakeParent(pr.Base): class FakeParent(pr.Base):
parent = None # To avoid having no parent for NamePart. parent = compiled.builtin
path = None
names = [] names = []
try: try:
@@ -67,8 +66,7 @@ class LazyName(helpers.FakeName):
module = builtins module = builtins
else: else:
module = __import__(module_name) module = __import__(module_name)
fake_name = helpers.FakeName(names, FakeParent()) parser_path = [helpers.FakeName(n, FakeParent()) for n in names]
parser_path = fake_name.names
raw_module = get_module(self._value) raw_module = get_module(self._value)
try: try:
-1
View File
@@ -1,7 +1,6 @@
import pydoc import pydoc
import keyword import keyword
from jedi.parser.representation import NamePart
from jedi._compatibility import is_py3 from jedi._compatibility import is_py3
from jedi import common from jedi import common
from jedi.evaluate import compiled from jedi.evaluate import compiled
+7 -9
View File
@@ -8,7 +8,7 @@ from jedi.evaluate import helpers
def usages(evaluator, definitions, mods): def usages(evaluator, definitions, mods):
""" """
:param definitions: list of NameParts :param definitions: list of Name
""" """
def compare_array(definitions): def compare_array(definitions):
""" `definitions` are being compared by module/start_pos, because """ `definitions` are being compared by module/start_pos, because
@@ -25,8 +25,7 @@ def usages(evaluator, definitions, mods):
while not stmt.parent.is_scope(): while not stmt.parent.is_scope():
stmt = stmt.parent stmt = stmt.parent
# New definition, call cannot be a part of stmt # New definition, call cannot be a part of stmt
if len(call.name) == 1 and call.next is None \ if call.next is None and call.name in stmt.get_defined_names():
and call.name in stmt.get_defined_names():
# Class params are not definitions (like function params). They # Class params are not definitions (like function params). They
# are super classes, that need to be resolved. # are super classes, that need to be resolved.
if not (isinstance(stmt, pr.Param) and isinstance(stmt.parent, pr.Class)): if not (isinstance(stmt, pr.Param) and isinstance(stmt.parent, pr.Class)):
@@ -35,7 +34,7 @@ def usages(evaluator, definitions, mods):
follow = [] # There might be multiple search_name's in one call_path follow = [] # There might be multiple search_name's in one call_path
call_path = list(call.generate_call_path()) call_path = list(call.generate_call_path())
for i, name in enumerate(call_path): for i, name in enumerate(call_path):
# name is `pr.NamePart`. # name is `pr.Name`.
if u(name) == search_name: if u(name) == search_name:
follow.append(call_path[:i + 1]) follow.append(call_path[:i + 1])
@@ -70,11 +69,10 @@ def usages(evaluator, definitions, mods):
if isinstance(stmt, pr.Import): if isinstance(stmt, pr.Import):
count = 0 count = 0
imps = [] imps = []
for i in stmt.get_all_import_names(): for name in stmt.get_all_import_names():
for name_part in i.names: count += 1
count += 1 if unicode(name) == search_name:
if unicode(name_part) == search_name: imps.append((count, name))
imps.append((count, name_part))
for used_count, name_part in imps: for used_count, name_part in imps:
i = imports.ImportWrapper(evaluator, stmt, kill_count=count - used_count, i = imports.ImportWrapper(evaluator, stmt, kill_count=count - used_count,
+1 -1
View File
@@ -243,7 +243,7 @@ def save_parser(path, name, parser, pickling=True):
class ParserPickling(object): class ParserPickling(object):
version = 17 version = 18
""" """
Version number (integer) for file system cache. Version number (integer) for file system cache.
+41 -23
View File
@@ -69,7 +69,7 @@ backtracking algorithm.
.. todo:: nonlocal statement, needed or can be ignored? (py3k) .. todo:: nonlocal statement, needed or can be ignored? (py3k)
""" """
import copy import copy
import itertools from itertools import tee, chain
from jedi._compatibility import next, hasattr, unicode from jedi._compatibility import next, hasattr, unicode
from jedi.parser import representation as pr from jedi.parser import representation as pr
@@ -172,20 +172,7 @@ class Evaluator(object):
return precedence.process_precedence_element(self, p) or [] return precedence.process_precedence_element(self, p) or []
def eval_statement_element(self, element): def eval_statement_element(self, element):
if pr.Array.is_type(element, pr.Array.NOARRAY): if isinstance(element, pr.ListComprehension):
try:
lst_cmp = element[0].expression_list()[0]
if not isinstance(lst_cmp, pr.ListComprehension):
raise IndexError
except IndexError:
r = list(itertools.chain.from_iterable(self.eval_statement(s)
for s in element))
else:
r = [iterable.GeneratorComprehension(self, lst_cmp)]
call_path = element.generate_call_path()
next(call_path, None) # the first one has been used already
return self.follow_path(call_path, r, element.parent)
elif isinstance(element, pr.ListComprehension):
return self.eval_statement(element.stmt) return self.eval_statement(element.stmt)
elif isinstance(element, pr.Lambda): elif isinstance(element, pr.Lambda):
return [er.Function(self, element)] return [er.Function(self, element)]
@@ -219,9 +206,20 @@ class Evaluator(object):
current = next(path) current = next(path)
if isinstance(current, pr.Array): if isinstance(current, pr.Array):
types = [iterable.Array(self, current)] if current.type == pr.Array.NOARRAY:
try:
lst_cmp = current[0].expression_list()[0]
if not isinstance(lst_cmp, pr.ListComprehension):
raise IndexError
except IndexError:
types = list(chain.from_iterable(self.eval_statement(s)
for s in current))
else:
types = [iterable.GeneratorComprehension(self, lst_cmp)]
else:
types = [iterable.Array(self, current)]
else: else:
if isinstance(current, pr.NamePart): if isinstance(current, pr.Name):
# This is the first global lookup. # This is the first global lookup.
types = self.find_types(scope, current, position=position, types = self.find_types(scope, current, position=position,
search_global=True) search_global=True)
@@ -241,7 +239,7 @@ class Evaluator(object):
to follow a call like ``module.a_type.Foo.bar`` (in ``from_somewhere``). to follow a call like ``module.a_type.Foo.bar`` (in ``from_somewhere``).
""" """
results_new = [] results_new = []
iter_paths = itertools.tee(path, len(types)) iter_paths = tee(path, len(types))
for i, typ in enumerate(types): for i, typ in enumerate(types):
fp = self._follow_path(iter_paths[i], typ, call_scope) fp = self._follow_path(iter_paths[i], typ, call_scope)
@@ -320,12 +318,26 @@ class Evaluator(object):
return types return types
def goto(self, stmt, call_path): def goto(self, stmt, call_path):
if isinstance(stmt, pr.Import):
# Nowhere to goto for aliases
if stmt.alias == call_path[0]:
return [call_path[0]]
names = stmt.get_all_import_names()
if stmt.alias:
names = names[:-1]
# Filter names that are after our Name
removed_names = len(names) - names.index(call_path[0]) - 1
i = imports.ImportWrapper(self, stmt, kill_count=removed_names,
nested_resolve=True)
return i.follow(is_goto=True)
# Return the name defined in the call_path, if it's part of the # Return the name defined in the call_path, if it's part of the
# statement name definitions. Only return, if it's one name and one # statement name definitions. Only return, if it's one name and one
# name only. Otherwise it's a mixture between a definition and a # name only. Otherwise it's a mixture between a definition and a
# reference. In this case it's just a definition. So we stay on it. # reference. In this case it's just a definition. So we stay on it.
if len(call_path) == 1 and isinstance(call_path[0], pr.NamePart) \ if len(call_path) == 1 and isinstance(call_path[0], pr.Name) \
and call_path[0] in [d.names[-1] for d in stmt.get_defined_names()]: and call_path[0] in stmt.get_defined_names():
# Named params should get resolved to their param definitions. # Named params should get resolved to their param definitions.
if pr.Array.is_type(stmt.parent, pr.Array.TUPLE, pr.Array.NOARRAY) \ if pr.Array.is_type(stmt.parent, pr.Array.TUPLE, pr.Array.NOARRAY) \
and stmt.parent.previous: and stmt.parent.previous:
@@ -337,9 +349,15 @@ class Evaluator(object):
param_names = [] param_names = []
named_param_name = stmt.get_defined_names()[0] named_param_name = stmt.get_defined_names()[0]
for typ in self.eval_call(call): for typ in self.eval_call(call):
for param in typ.params: if isinstance(typ, er.Class):
params = []
for init_method in typ.py__getattribute__('__init__'):
params += init_method.params
else:
params = typ.params
for param in params:
if unicode(param.get_name()) == unicode(named_param_name): if unicode(param.get_name()) == unicode(named_param_name):
param_names.append(param.get_name().names[-1]) param_names.append(param.get_name())
return param_names return param_names
return [call_path[0]] return [call_path[0]]
@@ -364,7 +382,7 @@ class Evaluator(object):
def filter_private_variable(scope, call_scope, var_name): def filter_private_variable(scope, call_scope, var_name):
"""private variables begin with a double underline `__`""" """private variables begin with a double underline `__`"""
var_name = str(var_name) # var_name could be a NamePart var_name = str(var_name) # var_name could be a Name
if isinstance(var_name, (str, unicode)) and isinstance(scope, er.Instance)\ if isinstance(var_name, (str, unicode)) and isinstance(scope, er.Instance)\
and var_name.startswith('__') and not var_name.endswith('__'): and var_name.startswith('__') and not var_name.endswith('__'):
s = call_scope.get_parent_until((pr.Class, er.Instance, compiled.CompiledObject)) s = call_scope.get_parent_until((pr.Class, er.Instance, compiled.CompiledObject))
-1
View File
@@ -240,7 +240,6 @@ class CompiledName(FakeName):
super(CompiledName, self).__init__(name) super(CompiledName, self).__init__(name)
self._obj = obj self._obj = obj
self.name = name self.name = name
self.start_pos = 0, 0 # an illegal start_pos, to make sorting easy.
def __repr__(self): def __repr__(self):
try: try:
+2 -2
View File
@@ -87,7 +87,7 @@ def search_params(evaluator, param):
# Need to take right index, because there could be a # Need to take right index, because there could be a
# func usage before. # func usage before.
call_path_simple = [unicode(d) if isinstance(d, pr.NamePart) call_path_simple = [unicode(d) if isinstance(d, pr.Name)
else d for d in call_path] else d for d in call_path]
i = listRightIndex(call_path_simple, func_name) i = listRightIndex(call_path_simple, func_name)
before, after = call_path[:i], call_path[i + 1:] before, after = call_path[:i], call_path[i + 1:]
@@ -121,7 +121,7 @@ def search_params(evaluator, param):
for params in get_posibilities(evaluator, module, func_name): for params in get_posibilities(evaluator, module, func_name):
for p in params: for p in params:
if str(p) == param_name: if str(p) == param_name:
result += evaluator.eval_statement(p.parent) result += evaluator.eval_statement(p.get_definition())
return result return result
func = param.get_parent_until(pr.Function) func = param.get_parent_until(pr.Function)
+19 -15
View File
@@ -42,7 +42,7 @@ class NameFinder(object):
types = self._names_to_types(names, resolve_decorator) types = self._names_to_types(names, resolve_decorator)
if not names and not types \ if not names and not types \
and not (isinstance(self.name_str, pr.NamePart) and not (isinstance(self.name_str, pr.Name)
and isinstance(self.name_str.parent.parent, pr.Param)): and isinstance(self.name_str.parent.parent, pr.Param)):
if not isinstance(self.name_str, (str, unicode)): # TODO Remove? if not isinstance(self.name_str, (str, unicode)): # TODO Remove?
if search_global: if search_global:
@@ -102,18 +102,18 @@ class NameFinder(object):
or isinstance(scope, compiled.CompiledObject) \ or isinstance(scope, compiled.CompiledObject) \
or isinstance(stmt, pr.ExprStmt) and stmt.is_global(): or isinstance(stmt, pr.ExprStmt) and stmt.is_global():
# Always reachable. # Always reachable.
names.append(name.names[-1]) names.append(name)
else: else:
check = flow_analysis.break_check(self._evaluator, check = flow_analysis.break_check(self._evaluator,
name_list_scope, name_list_scope,
er.wrap(self._evaluator, scope), er.wrap(self._evaluator, scope),
self.scope) self.scope)
if check is not flow_analysis.UNREACHABLE: if check is not flow_analysis.UNREACHABLE:
names.append(name.names[-1]) names.append(name)
if check is flow_analysis.REACHABLE: if check is flow_analysis.REACHABLE:
break break
if names and self._is_name_break_scope(name, stmt): if names and self._is_name_break_scope(stmt):
if self._does_scope_break_immediately(scope, name_list_scope): if self._does_scope_break_immediately(scope, name_list_scope):
break break
else: else:
@@ -139,16 +139,16 @@ class NameFinder(object):
evaluation, so remove them already here! evaluation, so remove them already here!
""" """
for n in names: for n in names:
definition = n.parent.parent definition = n.parent
if isinstance(definition, (pr.Function, pr.Class, pr.Module)): if isinstance(definition, (pr.Function, pr.Class, pr.Module)):
yield er.wrap(self._evaluator, definition).name.names[-1] yield er.wrap(self._evaluator, definition).name
else: else:
yield n yield n
def _check_getattr(self, inst): def _check_getattr(self, inst):
"""Checks for both __getattr__ and __getattribute__ methods""" """Checks for both __getattr__ and __getattribute__ methods"""
result = [] result = []
# str is important to lose the NamePart! # str is important, because it shouldn't be `Name`!
name = compiled.create(self._evaluator, str(self.name_str)) name = compiled.create(self._evaluator, str(self.name_str))
with common.ignored(KeyError): with common.ignored(KeyError):
result = inst.execute_subscope_by_name('__getattr__', [name]) result = inst.execute_subscope_by_name('__getattr__', [name])
@@ -161,12 +161,12 @@ class NameFinder(object):
result = inst.execute_subscope_by_name('__getattribute__', [name]) result = inst.execute_subscope_by_name('__getattribute__', [name])
return result return result
def _is_name_break_scope(self, name, stmt): def _is_name_break_scope(self, stmt):
""" """
Returns True except for nested imports and instance variables. Returns True except for nested imports and instance variables.
""" """
if stmt.isinstance(pr.ExprStmt): if stmt.isinstance(pr.ExprStmt):
if isinstance(name, er.InstanceElement) and not name.is_class_var: if isinstance(stmt, er.InstanceElement) and not stmt.is_class_var:
return False return False
elif isinstance(stmt, pr.Import) and stmt.is_nested(): elif isinstance(stmt, pr.Import) and stmt.is_nested():
return False return False
@@ -219,7 +219,7 @@ class NameFinder(object):
evaluator = self._evaluator evaluator = self._evaluator
# Add isinstance and other if/assert knowledge. # Add isinstance and other if/assert knowledge.
if isinstance(self.name_str, pr.NamePart): if isinstance(self.name_str, pr.Name):
flow_scope = self.name_str.parent.parent flow_scope = self.name_str.parent.parent
# Ignore FunctionExecution parents for now. # Ignore FunctionExecution parents for now.
until = flow_scope.get_parent_until(er.FunctionExecution) until = flow_scope.get_parent_until(er.FunctionExecution)
@@ -281,7 +281,7 @@ class NameFinder(object):
if isinstance(p, pr.Flow) and p.command == 'except' and p.inputs: if isinstance(p, pr.Flow) and p.command == 'except' and p.inputs:
as_names = p.inputs[0].as_names as_names = p.inputs[0].as_names
try: try:
if as_names[0].names[-1] == name: if as_names[0] == name:
# TODO check for types that are not classes and add it to # TODO check for types that are not classes and add it to
# the static analysis report. # the static analysis report.
types = list(chain.from_iterable( types = list(chain.from_iterable(
@@ -395,7 +395,7 @@ def check_flow_information(evaluator, flow, search_name_part, pos):
return result return result
def _check_isinstance_type(evaluator, stmt, search_name_part): def _check_isinstance_type(evaluator, stmt, search_name):
try: try:
expression_list = stmt.expression_list() expression_list = stmt.expression_list()
# this might be removed if we analyze and, etc # this might be removed if we analyze and, etc
@@ -412,8 +412,12 @@ def _check_isinstance_type(evaluator, stmt, search_name_part):
assert len(classes) == 1 assert len(classes) == 1
assert isinstance(obj[0], pr.Call) assert isinstance(obj[0], pr.Call)
# names fit? prev = search_name.parent
assert unicode(obj[0].name) == unicode(search_name_part.parent) while prev.previous is not None:
prev = prev.previous
# Do a simple get_code comparison. They should just have the same code,
# and everything will be all right.
assert obj[0].get_code() == prev.get_code()
assert isinstance(classes[0], pr.StatementElement) # can be type or tuple assert isinstance(classes[0], pr.StatementElement) # can be type or tuple
except AssertionError: except AssertionError:
return [] return []
@@ -583,7 +587,7 @@ def find_assignments(lhs, results, seek_name):
""" """
if isinstance(lhs, pr.Array): if isinstance(lhs, pr.Array):
return _assign_tuples(lhs, results, seek_name) return _assign_tuples(lhs, results, seek_name)
elif unicode(lhs.name.names[-1]) == seek_name: elif unicode(lhs.name) == seek_name:
return results return results
else: else:
return [] return []
+18 -17
View File
@@ -1,6 +1,7 @@
import copy import copy
from itertools import chain from itertools import chain
from jedi._compatibility import unicode
from jedi.parser import representation as pr from jedi.parser import representation as pr
from jedi import debug from jedi import debug
@@ -14,7 +15,7 @@ def deep_ast_copy(obj, new_elements_default=None):
return key_value[0] not in ('_expression_list', '_assignment_details') return key_value[0] not in ('_expression_list', '_assignment_details')
new_elements = new_elements_default or {} new_elements = new_elements_default or {}
accept = (pr.Simple, pr.NamePart, pr.KeywordStatement) accept = (pr.Simple, pr.Name, pr.KeywordStatement)
def recursion(obj): def recursion(obj):
# If it's already in the cache, just return it. # If it's already in the cache, just return it.
@@ -50,6 +51,8 @@ def deep_ast_copy(obj, new_elements_default=None):
# because there are several references that don't walk the whole # because there are several references that don't walk the whole
# tree in there. # tree in there.
items = sorted(items, key=sort_stmt) items = sorted(items, key=sort_stmt)
else:
items = sorted(items, key=lambda x: x[0] == '_names_dict')
# Actually copy and set attributes. # Actually copy and set attributes.
new_obj = copy.copy(obj) new_obj = copy.copy(obj)
@@ -67,13 +70,17 @@ def deep_ast_copy(obj, new_elements_default=None):
pass pass
elif key in ['parent_function', 'use_as_parent', '_sub_module']: elif key in ['parent_function', 'use_as_parent', '_sub_module']:
continue continue
elif key == '_names_dict':
d = dict((k, sequence_recursion(v)) for k, v in value.items())
setattr(new_obj, key, d)
elif isinstance(value, (list, tuple)): elif isinstance(value, (list, tuple)):
setattr(new_obj, key, list_or_tuple_rec(value)) setattr(new_obj, key, sequence_recursion(value))
elif isinstance(value, accept): elif isinstance(value, accept):
setattr(new_obj, key, recursion(value)) setattr(new_obj, key, recursion(value))
return new_obj return new_obj
def list_or_tuple_rec(array_obj): def sequence_recursion(array_obj):
if isinstance(array_obj, tuple): if isinstance(array_obj, tuple):
copied_array = list(array_obj) copied_array = list(array_obj)
else: else:
@@ -82,7 +89,7 @@ def deep_ast_copy(obj, new_elements_default=None):
if isinstance(el, accept): if isinstance(el, accept):
copied_array[i] = recursion(el) copied_array[i] = recursion(el)
elif isinstance(el, (tuple, list)): elif isinstance(el, (tuple, list)):
copied_array[i] = list_or_tuple_rec(el) copied_array[i] = sequence_recursion(el)
if isinstance(array_obj, tuple): if isinstance(array_obj, tuple):
return tuple(copied_array) return tuple(copied_array)
@@ -196,9 +203,7 @@ def scan_statement_for_calls(stmt, search_name, assignment_details=False):
if isinstance(s_new, pr.Array): if isinstance(s_new, pr.Array):
result += scan_array(s_new, search_name) result += scan_array(s_new, search_name)
else: else:
n = s_new.name if search_name == unicode(s_new.name):
if isinstance(n, pr.Name) \
and search_name in [str(x) for x in n.names]:
result.append(c) result.append(c)
s_new = s_new.next s_new = s_new.next
@@ -217,7 +222,7 @@ def get_module_name_parts(module):
def scope_name_parts(scope): def scope_name_parts(scope):
for s in scope.subscopes: for s in scope.subscopes:
# Yield the name parts, not names. # Yield the name parts, not names.
yield s.name.names[0] yield s.name
for need_yield_from in scope_name_parts(s): for need_yield_from in scope_name_parts(s):
yield need_yield_from yield need_yield_from
@@ -226,7 +231,7 @@ def get_module_name_parts(module):
for stmt_or_import in statements_or_imports: for stmt_or_import in statements_or_imports:
if isinstance(stmt_or_import, pr.Import): if isinstance(stmt_or_import, pr.Import):
for name in stmt_or_import.get_all_import_names(): for name in stmt_or_import.get_all_import_names():
name_parts.update(name.names) name_parts.add(name)
else: else:
# Running this ensures that all the expression lists are generated # Running this ensures that all the expression lists are generated
# and the parents are all set. (Important for Lambdas) Howeer, this # and the parents are all set. (Important for Lambdas) Howeer, this
@@ -238,7 +243,7 @@ def get_module_name_parts(module):
# all the name_parts. # all the name_parts.
for tok in stmt_or_import._token_list: for tok in stmt_or_import._token_list:
if isinstance(tok, pr.Name): if isinstance(tok, pr.Name):
name_parts.update(tok.names) name_parts.add(tok)
return name_parts return name_parts
@@ -298,18 +303,14 @@ class FakeStatement(pr.ExprStmt):
class FakeImport(pr.Import): class FakeImport(pr.Import):
def __init__(self, name, parent, level=0): def __init__(self, name, parent, level=0):
p = 0, 0 p = 0, 0
super(FakeImport, self).__init__(FakeSubModule, p, p, name, super(FakeImport, self).__init__(FakeSubModule, p, p, [name],
relative_count=level) relative_count=level)
self.parent = parent self.parent = parent
class FakeName(pr.Name): class FakeName(pr.Name):
def __init__(self, name_or_names, parent=None, start_pos=(0, 0)): def __init__(self, name_str, parent=None, start_pos=(0, 0)):
if isinstance(name_or_names, list): super(FakeName, self).__init__(FakeSubModule, name_str, parent, start_pos)
names = [(n, start_pos) for n in name_or_names]
else:
names = [(name_or_names, start_pos)]
super(FakeName, self).__init__(FakeSubModule, names, start_pos, start_pos, parent)
def get_definition(self): def get_definition(self):
return self.parent return self.parent
+70 -65
View File
@@ -67,13 +67,13 @@ class ImportWrapper(pr.Base):
# rest is import_path resolution # rest is import_path resolution
import_path = [] import_path = []
if import_stmt.from_ns: if import_stmt.from_names:
import_path += import_stmt.from_ns.names import_path += import_stmt.from_names
if import_stmt.namespace: if import_stmt.namespace_names:
if self.import_stmt.is_nested() and not nested_resolve: if self.import_stmt.is_nested() and not nested_resolve:
import_path.append(import_stmt.namespace.names[0]) import_path.append(import_stmt.namespace_names[0])
else: else:
import_path += import_stmt.namespace.names import_path += import_stmt.namespace_names
for i in range(kill_count + int(is_like_search)): for i in range(kill_count + int(is_like_search)):
if import_path: if import_path:
@@ -110,6 +110,7 @@ class ImportWrapper(pr.Base):
m = _load_module(rel_path) m = _load_module(rel_path)
names += m.get_defined_names() names += m.get_defined_names()
else: else:
# flask
if self.import_path == ('flask', 'ext'): if self.import_path == ('flask', 'ext'):
# List Flask extensions like ``flask_foo`` # List Flask extensions like ``flask_foo``
for mod in self._get_module_names(): for mod in self._get_module_names():
@@ -122,6 +123,8 @@ class ImportWrapper(pr.Base):
flaskext = os.path.join(dir, 'flaskext') flaskext = os.path.join(dir, 'flaskext')
if os.path.isdir(flaskext): if os.path.isdir(flaskext):
names += self._get_module_names([flaskext]) names += self._get_module_names([flaskext])
# namespace packages
if on_import_stmt and isinstance(scope, pr.Module) \ if on_import_stmt and isinstance(scope, pr.Module) \
and scope.path.endswith('__init__.py'): and scope.path.endswith('__init__.py'):
pkg_path = os.path.dirname(scope.path) pkg_path = os.path.dirname(scope.path)
@@ -136,18 +139,18 @@ class ImportWrapper(pr.Base):
# ``sys.modules`` modification. # ``sys.modules`` modification.
names.append(self._generate_name('path')) names.append(self._generate_name('path'))
continue continue
if not self.import_stmt.from_names or self.is_partial_import:
# from_names must be defined to access module
# values plus a partial import means that there
# is something after the import, which
# automatically implies that there must not be
# any non-module scope.
continue
from jedi.evaluate import finder from jedi.evaluate import finder
for s, scope_names in finder.get_names_of_scope(self._evaluator, for s, scope_names in finder.get_names_of_scope(self._evaluator,
scope, include_builtin=False): scope, include_builtin=False):
for n in scope_names: for n in scope_names:
if self.import_stmt.from_ns is None \
or self.is_partial_import:
# from_ns must be defined to access module
# values plus a partial import means that there
# is something after the import, which
# automatically implies that there must not be
# any non-module scope.
continue
names.append(n) names.append(n)
return names return names
@@ -179,55 +182,57 @@ class ImportWrapper(pr.Base):
# check recursion # check recursion
return [] return []
if self.import_path: try:
try: if self.import_path:
module, rest = self._importer.follow_file_system() try:
except ModuleNotFound as e: module, rest = self._importer.follow_file_system()
analysis.add(self._evaluator, 'import-error', e.name_part) except ModuleNotFound as e:
return [] analysis.add(self._evaluator, 'import-error', e.name_part)
return []
if module is None: if module is None:
return [] return []
if self.import_stmt.is_nested() and not self.nested_resolve: if self.import_stmt.is_nested() and not self.nested_resolve:
scopes = [NestedImportModule(module, self.import_stmt)] scopes = [NestedImportModule(module, self.import_stmt)]
else:
scopes = [module]
star_imports = remove_star_imports(self._evaluator, module)
if star_imports:
scopes = [StarImportModule(scopes[0], star_imports)]
# goto only accepts Names or NameParts
if is_goto and not rest:
scopes = [s.name.names[-1] for s in scopes]
# follow the rest of the import (not FS -> classes, functions)
if len(rest) > 1 or rest and self.is_like_search:
scopes = []
if ('os', 'path') == self.import_path[:2] \
and not self._is_relative_import():
# This is a huge exception, we follow a nested import
# ``os.path``, because it's a very important one in Python
# that is being achieved by messing with ``sys.modules`` in
# ``os``.
scopes = self._evaluator.follow_path(iter(rest), [module], module)
elif rest:
if is_goto:
scopes = list(chain.from_iterable(
self._evaluator.find_types(s, rest[0], is_goto=True)
for s in scopes))
else: else:
scopes = list(chain.from_iterable( scopes = [module]
self._evaluator.follow_path(iter(rest), [s], s)
for s in scopes)) star_imports = remove_star_imports(self._evaluator, module)
else: if star_imports:
scopes = [ImportWrapper.GlobalNamespace] scopes = [StarImportModule(scopes[0], star_imports)]
debug.dbg('after import: %s', scopes)
if not scopes: # goto only accepts `Name`
analysis.add(self._evaluator, 'import-error', if is_goto and not rest:
self._importer.import_path[-1]) scopes = [s.name for s in scopes]
self._evaluator.recursion_detector.pop_stmt()
# follow the rest of the import (not FS -> classes, functions)
if len(rest) > 1 or rest and self.is_like_search:
scopes = []
if ('os', 'path') == self.import_path[:2] \
and not self._is_relative_import():
# This is a huge exception, we follow a nested import
# ``os.path``, because it's a very important one in Python
# that is being achieved by messing with ``sys.modules`` in
# ``os``.
scopes = self._evaluator.follow_path(iter(rest), [module], module)
elif rest:
if is_goto:
scopes = list(chain.from_iterable(
self._evaluator.find_types(s, rest[0], is_goto=True)
for s in scopes))
else:
scopes = list(chain.from_iterable(
self._evaluator.follow_path(iter(rest), [s], s)
for s in scopes))
else:
scopes = [ImportWrapper.GlobalNamespace]
debug.dbg('after import: %s', scopes)
if not scopes:
analysis.add(self._evaluator, 'import-error',
self._importer.import_path[-1])
finally:
self._evaluator.recursion_detector.pop_stmt()
return scopes return scopes
@@ -244,12 +249,12 @@ class NestedImportModule(pr.Module):
# This is not an existing Import statement. Therefore, set position to # This is not an existing Import statement. Therefore, set position to
# 0 (0 is not a valid line number). # 0 (0 is not a valid line number).
zero = (0, 0) zero = (0, 0)
names = [unicode(name_part) for name_part in i.namespace.names[1:]] names = [unicode(name_part) for name_part in i.namespace_names[1:]]
name = helpers.FakeName(names, self._nested_import) name = helpers.FakeName(names, self._nested_import)
new = pr.Import(i._sub_module, zero, zero, name) new = pr.Import(i._sub_module, zero, zero, name)
new.parent = self._module new.parent = self._module
debug.dbg('Generated a nested import: %s', new) debug.dbg('Generated a nested import: %s', new)
return helpers.FakeName(str(i.namespace.names[1]), new) return helpers.FakeName(str(i.namespace_names[1]), new)
def _get_defined_names(self): def _get_defined_names(self):
""" """
@@ -330,7 +335,7 @@ class _Importer(object):
self.file_path = os.path.dirname(path) if path is not None else None self.file_path = os.path.dirname(path) if path is not None else None
def str_import_path(self): def str_import_path(self):
"""Returns the import path as pure strings instead of NameParts.""" """Returns the import path as pure strings instead of `Name`."""
return tuple(str(name_part) for name_part in self.import_path) return tuple(str(name_part) for name_part in self.import_path)
def get_relative_path(self): def get_relative_path(self):
@@ -372,12 +377,12 @@ class _Importer(object):
pos = (part._line, part._column) pos = (part._line, part._column)
try: try:
self.import_path = ( self.import_path = (
pr.NamePart(FakeSubModule, 'flask_' + str(part), part.parent, pos), pr.Name(FakeSubModule, 'flask_' + str(part), part.parent, pos),
) + orig_path[3:] ) + orig_path[3:]
return self._real_follow_file_system() return self._real_follow_file_system()
except ModuleNotFound as e: except ModuleNotFound as e:
self.import_path = ( self.import_path = (
pr.NamePart(FakeSubModule, 'flaskext', part.parent, pos), pr.Name(FakeSubModule, 'flaskext', part.parent, pos),
) + orig_path[2:] ) + orig_path[2:]
return self._real_follow_file_system() return self._real_follow_file_system()
return self._real_follow_file_system() return self._real_follow_file_system()
@@ -606,5 +611,5 @@ def get_modules_containing_name(mods, name):
for p in sorted(paths): for p in sorted(paths):
# make testing easier, sort it - same results on every interpreter # make testing easier, sort it - same results on every interpreter
c = check_python_file(p) c = check_python_file(p)
if c is not None and c not in mods: if c is not None and c not in mods and not isinstance(c, compiled.CompiledObject):
yield c yield c
+3 -31
View File
@@ -193,14 +193,14 @@ class Array(use_metaclass(CachedMetaClass, IterableWrapper)):
def scope_names_generator(self, position=None): def scope_names_generator(self, position=None):
""" """
This method generates all `ArrayMethod` for one pr.Array.
It returns e.g. for a list: append, pop, ... It returns e.g. for a list: append, pop, ...
""" """
# `array.type` is a string with the type, e.g. 'list'. # `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.find_types(compiled.builtin, self._array.type)[0]
scope = self._evaluator.execute(scope)[0] # builtins only have one class 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(): for _, names in scope.scope_names_generator():
yield self, [ArrayMethod(n) for n in names] yield self, [get_instance_el(self._evaluator, self, n) for n in names]
@common.safe_property @common.safe_property
def parent(self): def parent(self):
@@ -225,34 +225,6 @@ class Array(use_metaclass(CachedMetaClass, IterableWrapper)):
return "<e%s of %s>" % (type(self).__name__, self._array) return "<e%s of %s>" % (type(self).__name__, self._array)
class ArrayMethod(IterableWrapper):
"""
A name, e.g. `list.append`, it is used to access the original array
methods.
"""
def __init__(self, name):
super(ArrayMethod, self).__init__()
self.name = name
@property
@underscore_memoization
def names(self):
# TODO remove this method, we need the ArrayMethod input to be a NamePart.
return [pr.NamePart(self.name._sub_module, unicode(n), self, n.start_pos) for n in self.name.names]
def __getattr__(self, name):
# Set access privileges:
if name not in ['parent', 'start_pos', 'end_pos', 'get_code', 'get_definition']:
raise AttributeError('Strange access on %s: %s.' % (self, name))
return getattr(self.name, name)
def get_parent_until(self):
return compiled.builtin
def __repr__(self):
return "<%s of %s>" % (type(self).__name__, self.name)
class MergedArray(Array): class MergedArray(Array):
def __init__(self, evaluator, arrays): def __init__(self, evaluator, arrays):
super(MergedArray, self).__init__(evaluator, arrays[-1]._array) super(MergedArray, self).__init__(evaluator, arrays[-1]._array)
@@ -342,7 +314,7 @@ def _check_array_additions(evaluator, compare_array, module, is_list):
result = [] result = []
for c in calls: for c in calls:
call_path = list(c.generate_call_path()) call_path = list(c.generate_call_path())
call_path_simple = [unicode(n) if isinstance(n, pr.NamePart) else n call_path_simple = [unicode(n) if isinstance(n, pr.Name) else n
for n in call_path] for n in call_path]
separate_index = call_path_simple.index(add_name) separate_index = call_path_simple.index(add_name)
if add_name == call_path_simple[-1] or separate_index == 0: if add_name == call_path_simple[-1] or separate_index == 0:
+4 -4
View File
@@ -1,9 +1,10 @@
import copy import copy
from jedi._compatibility import unicode, zip_longest from jedi._compatibility import unicode, zip_longest
from jedi import debug
from jedi import common
from jedi.parser import representation as pr from jedi.parser import representation as pr
from jedi.evaluate import iterable from jedi.evaluate import iterable
from jedi import common
from jedi.evaluate import helpers from jedi.evaluate import helpers
from jedi.evaluate import analysis from jedi.evaluate import analysis
@@ -294,7 +295,7 @@ def _iterate_star_args(evaluator, array, expression_list, func):
for field_stmt in array.iter_content(): for field_stmt in array.iter_content():
yield helpers.FakeStatement([field_stmt]) yield helpers.FakeStatement([field_stmt])
elif isinstance(array, Instance) and array.name.get_code() == 'tuple': elif isinstance(array, Instance) and array.name.get_code() == 'tuple':
pass debug.warning('Ignored a tuple *args input %s' % array)
else: else:
if expression_list: if expression_list:
m = "TypeError: %s() argument after * must be a sequence, not %s" \ m = "TypeError: %s() argument after * must be a sequence, not %s" \
@@ -320,6 +321,7 @@ def _star_star_dict(evaluator, array, expression_list, func):
elif isinstance(call, pr.Call): elif isinstance(call, pr.Call):
key = call.name key = call.name
else: else:
debug.warning('Ignored complicated **kwargs stmt %s' % call)
continue # We ignore complicated statements here, for now. continue # We ignore complicated statements here, for now.
# If the string is a duplicate, we don't care it's illegal Python # If the string is a duplicate, we don't care it's illegal Python
@@ -359,8 +361,6 @@ def _gen_param_name_copy(func, var_args, param, keys=(), values=(), array_type=N
new_param.set_expression_list([arr]) new_param.set_expression_list([arr])
name = copy.copy(param.get_name()) name = copy.copy(param.get_name())
name.names = [copy.copy(name.names[0])]
name.names[0].parent = name
name.parent = new_param name.parent = new_param
return name return name
+57 -20
View File
@@ -164,12 +164,14 @@ class Instance(use_metaclass(CachedMetaClass, Executed)):
# because to follow them and their self variables is too # because to follow them and their self variables is too
# complicated. # complicated.
sub = self._get_method_execution(sub) sub = self._get_method_execution(sub)
for n in sub.get_defined_names(): for per_name_list in sub.get_names_dict().values():
# Only names with the selfname are being added. for call in per_name_list:
# It is also important, that they have a len() of 2, if unicode(call.name) == self_name \
# because otherwise, they are just something else and isinstance(call.next, pr.Call) \
if unicode(n.names[0]) == self_name and len(n.names) == 2: and call.next.next is None:
add_self_dot_name(n) names.append(get_instance_el(self._evaluator, self, call.next.name))
#if unicode(n.names[0]) == self_name and len(n.names) == 2:
# add_self_dot_name(n)
for s in self.base.py__bases__(self._evaluator): for s in self.base.py__bases__(self._evaluator):
if not isinstance(s, compiled.CompiledObject): if not isinstance(s, compiled.CompiledObject):
@@ -243,7 +245,12 @@ def get_instance_el(evaluator, instance, var, is_class_var=False):
untouched. untouched.
""" """
if isinstance(var, (Instance, compiled.CompiledObject, pr.Operator, Token, if isinstance(var, (Instance, compiled.CompiledObject, pr.Operator, Token,
pr.Module, FunctionExecution)): pr.Module, FunctionExecution, pr.Name)):
if isinstance(var, pr.Name):
# TODO temp solution, remove later, Name should never get
# here?
par = get_instance_el(evaluator, instance, var.parent, is_class_var)
return pr.Name(var._sub_module, unicode(var), par, var.start_pos)
return var return var
var = wrap(evaluator, var) var = wrap(evaluator, var)
@@ -275,6 +282,9 @@ class InstanceElement(use_metaclass(CachedMetaClass, pr.Base)):
return par return par
def get_parent_until(self, *args, **kwargs): def get_parent_until(self, *args, **kwargs):
if isinstance(self.var, pr.Name):
# TODO Name should never even be InstanceElements
return pr.Simple.get_parent_until(self.parent, *args, **kwargs)
return pr.Simple.get_parent_until(self, *args, **kwargs) return pr.Simple.get_parent_until(self, *args, **kwargs)
def get_definition(self): def get_definition(self):
@@ -291,12 +301,6 @@ class InstanceElement(use_metaclass(CachedMetaClass, pr.Base)):
return [get_instance_el(self._evaluator, self.instance, command, self.is_class_var) return [get_instance_el(self._evaluator, self.instance, command, self.is_class_var)
for command in self.var.expression_list()] for command in self.var.expression_list()]
@property
@underscore_memoization
def names(self):
return [pr.NamePart(helpers.FakeSubModule, unicode(n), self, n.start_pos)
for n in self.var.names]
@property @property
@underscore_memoization @underscore_memoization
def name(self): def name(self):
@@ -390,6 +394,9 @@ class Class(use_metaclass(CachedMetaClass, Wrapper)):
def py__call__(self, evaluator, params): def py__call__(self, evaluator, params):
return [Instance(evaluator, self, params)] return [Instance(evaluator, self, params)]
def py__getattribute__(self, name):
return self._evaluator.find_types(self, name)
def scope_names_generator(self, position=None, add_class_vars=True): def scope_names_generator(self, position=None, add_class_vars=True):
def in_iterable(name, iterable): def in_iterable(name, iterable):
""" checks if the name is in the variable 'iterable'. """ """ checks if the name is in the variable 'iterable'. """
@@ -522,6 +529,21 @@ class Function(use_metaclass(CachedMetaClass, Wrapper)):
return "<e%s of %s%s>" % (type(self).__name__, self.base_func, dec) return "<e%s of %s%s>" % (type(self).__name__, self.base_func, dec)
class LazyDict(object):
def __init__(self, old_dct, copy_func):
self._copy_func = copy_func
self._old_dct = old_dct
def __getitem__(self, key):
return self._copy_func(self._old_dct[key])
@underscore_memoization
def values(self):
# TODO REMOVE this. Not necessary with correct name lookups.
for calls in self._old_dct.values():
yield self._copy_func(calls)
class FunctionExecution(Executed): class FunctionExecution(Executed):
""" """
This class is used to evaluate functions and their returns. This class is used to evaluate functions and their returns.
@@ -570,6 +592,10 @@ class FunctionExecution(Executed):
break break
return types return types
@underscore_memoization
def get_names_dict(self):
return LazyDict(self.base.get_names_dict(), self._copy_list)
@memoize_default(default=()) @memoize_default(default=())
def _get_params(self): def _get_params(self):
""" """
@@ -591,15 +617,13 @@ class FunctionExecution(Executed):
names = pr.filter_after_position(pr.Scope.get_defined_names(self), position) names = pr.filter_after_position(pr.Scope.get_defined_names(self), position)
yield self, self._get_params() + names yield self, self._get_params() + names
def _copy_list(self, list_name): def _copy_list(self, lst):
""" """
Copies a list attribute of a parser Function. Copying is very Copies a list attribute of a parser Function. Copying is very
expensive, because it is something like `copy.deepcopy`. However, these expensive, because it is something like `copy.deepcopy`. However, these
copied objects can be used for the executions, as if they were in the copied objects can be used for the executions, as if they were in the
execution. execution.
""" """
# Copy all these lists into this local function.
lst = getattr(self.base, list_name)
objects = [] objects = []
for element in lst: for element in lst:
self._scope_copy(element.parent) self._scope_copy(element.parent)
@@ -622,22 +646,22 @@ class FunctionExecution(Executed):
@common.safe_property @common.safe_property
@memoize_default([]) @memoize_default([])
def returns(self): def returns(self):
return self._copy_list('returns') return self._copy_list(self.base.returns)
@common.safe_property @common.safe_property
@memoize_default([]) @memoize_default([])
def asserts(self): def asserts(self):
return self._copy_list('asserts') return self._copy_list(self.base.asserts)
@common.safe_property @common.safe_property
@memoize_default([]) @memoize_default([])
def statements(self): def statements(self):
return self._copy_list('statements') return self._copy_list(self.base.statements)
@common.safe_property @common.safe_property
@memoize_default([]) @memoize_default([])
def subscopes(self): def subscopes(self):
return self._copy_list('subscopes') return self._copy_list(self.base.subscopes)
def get_statement_for_position(self, pos): def get_statement_for_position(self, pos):
return pr.Scope.get_statement_for_position(self, pos) return pr.Scope.get_statement_for_position(self, pos)
@@ -667,6 +691,11 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, pr.Module, Wrapper)):
# All the additional module attributes are strings. # All the additional module attributes are strings.
return [helpers.LazyName(n, parent_callback) for n in names] return [helpers.LazyName(n, parent_callback) for n in names]
@property
@memoize_default()
def name(self):
return pr.Name(self, unicode(self.base.name), self, (1, 0))
@memoize_default() @memoize_default()
def _sub_modules(self): def _sub_modules(self):
""" """
@@ -683,6 +712,14 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, pr.Module, Wrapper)):
imp = helpers.FakeImport(name, self, level=1) imp = helpers.FakeImport(name, self, level=1)
name.parent = imp name.parent = imp
names.append(name) names.append(name)
# TODO add something like this in the future, its cleaner than the
# import hacks.
# ``os.path`` is a hardcoded exception, because it's a
# ``sys.modules`` modification.
#if str(self.name) == 'os':
# names.append(helpers.FakeName('path', parent=self))
return names return names
def __getattr__(self, name): def __getattr__(self, name):
+8 -10
View File
@@ -60,7 +60,7 @@ def _paths_from_assignment(evaluator, statement):
for exp_list, operator in statement.assignment_details: for exp_list, operator in statement.assignment_details:
if len(exp_list) != 1 or not isinstance(exp_list[0], pr.Call): if len(exp_list) != 1 or not isinstance(exp_list[0], pr.Call):
continue continue
if unicode(exp_list[0].name) != 'sys.path': if exp_list[0].names() != ['sys', 'path']:
continue continue
# TODO at this point we ignore all ways what could be assigned to # TODO at this point we ignore all ways what could be assigned to
# sys.path or an execution of it. Here we could do way more # sys.path or an execution of it. Here we could do way more
@@ -88,17 +88,15 @@ def _paths_from_insert(module_path, exe):
def _paths_from_call_expression(module_path, call): def _paths_from_call_expression(module_path, call):
""" extract the path from either "sys.path.append" or "sys.path.insert" """ """ extract the path from either "sys.path.append" or "sys.path.insert" """
if not call.next_is_execution(): names = call.names()
return if names[:3] != ['sys', 'path', 'append'] and names[:3] != ['sys', 'path', 'insert']:
return []
if not call.next.next.next_is_execution():
return []
n = call.name
if not isinstance(n, pr.Name) or len(n.names) != 3:
return
names = [unicode(x) for x in n.names]
if names[:2] != ['sys', 'path']:
return
cmd = names[2] cmd = names[2]
exe = call.next exe = call.next.next.next
path = None
if cmd == 'insert' and len(exe) == 2: if cmd == 'insert' and len(exe) == 2:
path = _paths_from_insert(module_path, exe) path = _paths_from_insert(module_path, exe)
elif cmd == 'append' and len(exe) == 1: elif cmd == 'append' and len(exe) == 1:
+45 -45
View File
@@ -100,38 +100,46 @@ class Parser(object):
except KeyError: except KeyError:
self.module.used_names[tok_name] = set([simple]) self.module.used_names[tok_name] = set([simple])
self.module.temp_used_names = [] self.module.temp_used_names = []
if isinstance(simple, pr.Statement):
for name, calls in simple.get_names_dict().items():
self._scope.add_name_calls(name, calls)
def _parse_dot_name(self, pre_used_token=None): def _parse_dotted_name(self, pre_used_token=None):
""" """
The dot name parser parses a name, variable or function and returns The dot name parser parses a name, variable or function and returns
their names. their names.
Just used for parsing imports.
:return: tuple of Name, next_token :return: tuple of Name, next_token
""" """
def append(el): def append(tok):
names.append(el) names.append(pr.Name(self.module, tok.string, None, tok.start_pos))
self.module.temp_used_names.append(el[0]) self.module.temp_used_names.append(tok.string)
names = [] names = []
tok = next(self._gen) if pre_used_token is None else pre_used_token tok = next(self._gen) if pre_used_token is None else pre_used_token
if tok.type != tokenize.NAME and tok.string != '*': if tok.type != tokenize.NAME and tok.string != '*':
return None, tok return [], tok
first_pos = tok.start_pos append(tok)
append((tok.string, first_pos))
while True: while True:
end_pos = tok.end_pos
tok = next(self._gen) tok = next(self._gen)
if tok.string != '.': if tok.string != '.':
break break
tok = next(self._gen) tok = next(self._gen)
if tok.type != tokenize.NAME: if tok.type != tokenize.NAME:
break break
append((tok.string, tok.start_pos)) append(tok)
n = pr.Name(self.module, names, first_pos, end_pos) if names else None return names, tok
return n, tok
def _parse_name(self, pre_used_token=None):
tok = next(self._gen) if pre_used_token is None else pre_used_token
self.module.temp_used_names.append(tok.string)
if tok.type != tokenize.NAME:
return None, tok
return pr.Name(self.module, tok.string, None, tok.start_pos), next(self._gen)
def _parse_import_list(self): def _parse_import_list(self):
""" """
@@ -161,13 +169,13 @@ class Parser(object):
tok = next(self._gen) tok = next(self._gen)
if brackets and tok.type == tokenize.NEWLINE: if brackets and tok.type == tokenize.NEWLINE:
tok = next(self._gen) tok = next(self._gen)
i, tok = self._parse_dot_name(tok) names, tok = self._parse_dotted_name(tok)
if not i: if not names:
defunct = True defunct = True
name2 = None alias = None
if tok.string == 'as': if tok.string == 'as':
name2, tok = self._parse_dot_name() alias, tok = self._parse_name()
imports.append((i, name2, defunct)) imports.append((names, alias, defunct))
while tok.string not in continue_kw: while tok.string not in continue_kw:
tok = next(self._gen) tok = next(self._gen)
if not (tok.string == "," or brackets and tok.type == tokenize.NEWLINE): if not (tok.string == "," or brackets and tok.type == tokenize.NEWLINE):
@@ -224,10 +232,8 @@ class Parser(object):
if tok.type != tokenize.NAME: if tok.type != tokenize.NAME:
return None return None
fname = pr.Name(self.module, [(tok.string, tok.start_pos)], tok.start_pos, fname, tok = self._parse_name(tok)
tok.end_pos)
tok = next(self._gen)
if tok.string != '(': if tok.string != '(':
return None return None
params = self._parse_parentheses(is_class=False) params = self._parse_parentheses(is_class=False)
@@ -246,7 +252,7 @@ class Parser(object):
if colon.string != ':': if colon.string != ':':
return None return None
# because of 2 line func param definitions # Because of 2 line func param definitions
return pr.Function(self.module, fname, params, first_pos, annotation) return pr.Function(self.module, fname, params, first_pos, annotation)
def _parse_class(self): def _parse_class(self):
@@ -264,11 +270,9 @@ class Parser(object):
cname.start_pos[0], tokenize.tok_name[cname.type], cname.string) cname.start_pos[0], tokenize.tok_name[cname.type], cname.string)
return None return None
cname = pr.Name(self.module, [(cname.string, cname.start_pos)], cname, _next = self._parse_name(cname)
cname.start_pos, cname.end_pos)
superclasses = [] superclasses = []
_next = next(self._gen)
if _next.string == '(': if _next.string == '(':
superclasses = self._parse_parentheses(is_class=True) superclasses = self._parse_parentheses(is_class=True)
_next = next(self._gen) _next = next(self._gen)
@@ -342,7 +346,7 @@ class Parser(object):
if tok.string == 'as': if tok.string == 'as':
tok = next(self._gen) tok = next(self._gen)
if tok.type == tokenize.NAME: if tok.type == tokenize.NAME:
n, tok = self._parse_dot_name(self._gen.current) n, tok = self._parse_name(self._gen.current)
if n: if n:
set_vars.append(n) set_vars.append(n)
as_names.append(n) as_names.append(n)
@@ -354,11 +358,7 @@ class Parser(object):
elif in_lambda_param and tok.string == ':': elif in_lambda_param and tok.string == ':':
in_lambda_param = False in_lambda_param = False
elif tok.type == tokenize.NAME and not is_kw: elif tok.type == tokenize.NAME and not is_kw:
n, tok = self._parse_dot_name(self._gen.current) tok_list[-1], tok = self._parse_name(tok)
# removed last entry, because we add Name
tok_list.pop()
if n:
tok_list.append(n)
continue continue
elif tok.string in opening_brackets: elif tok.string in opening_brackets:
level += 1 level += 1
@@ -460,10 +460,10 @@ class Parser(object):
# import stuff # import stuff
elif tok_str == 'import': elif tok_str == 'import':
imports = self._parse_import_list() imports = self._parse_import_list()
for count, (m, alias, defunct) in enumerate(imports): for count, (names, alias, defunct) in enumerate(imports):
e = (alias or m or self._gen.previous).end_pos e = (alias or names and names[-1] or self._gen.previous).end_pos
end_pos = self._gen.previous.end_pos if count + 1 == len(imports) else e end_pos = self._gen.previous.end_pos if count + 1 == len(imports) else e
i = pr.Import(self.module, first_pos, end_pos, m, i = pr.Import(self.module, first_pos, end_pos, names,
alias, defunct=defunct) alias, defunct=defunct)
self._check_user_stmt(i) self._check_user_stmt(i)
self._scope.add_import(i) self._scope.add_import(i)
@@ -482,26 +482,26 @@ class Parser(object):
break break
relative_count += 1 relative_count += 1
# the from import # the from import
mod, tok = self._parse_dot_name(self._gen.current) from_names, tok = self._parse_dotted_name(self._gen.current)
tok_str = tok.string tok_str = tok.string
if str(mod) == 'import' and relative_count: if len(from_names) == 1 and str(from_names[0]) == 'import' and relative_count:
self._gen.push_last_back() self._gen.push_last_back()
tok_str = 'import' tok_str = 'import'
mod = None from_names = []
if not mod and not relative_count or tok_str != "import": if not from_names and not relative_count or tok_str != "import":
debug.warning("from: syntax error@%s", tok.start_pos[0]) debug.warning("from: syntax error@%s", tok.start_pos[0])
defunct = True defunct = True
if tok_str != 'import': if tok_str != 'import':
self._gen.push_last_back() self._gen.push_last_back()
names = self._parse_import_list() imports = self._parse_import_list()
for count, (name, alias, defunct2) in enumerate(names): for count, (names, alias, defunct2) in enumerate(imports):
star = name is not None and unicode(name.names[0]) == '*' star = names and unicode(names[-1]) == '*'
if star: if star:
name = None names = []
e = (alias or name or self._gen.previous).end_pos e = (alias or names and names[-1] or self._gen.previous).end_pos
end_pos = self._gen.previous.end_pos if count + 1 == len(names) else e #end_pos = self._gen.previous.end_pos if count + 1 == len(names) else e
i = pr.Import(self.module, first_pos, end_pos, name, i = pr.Import(self.module, first_pos, e, names,
alias, mod, star, relative_count, alias, from_names, star, relative_count,
defunct=defunct or defunct2) defunct=defunct or defunct2)
self._check_user_stmt(i) self._check_user_stmt(i)
self._scope.add_import(i) self._scope.add_import(i)
@@ -536,7 +536,7 @@ class Parser(object):
if command == 'except' and tok.string == ',': if command == 'except' and tok.string == ',':
# the except statement defines a var # the except statement defines a var
# this is only true for python 2 # this is only true for python 2
n, tok = self._parse_dot_name() n, tok = self._parse_name()
if n: if n:
n.parent = statement n.parent = statement
statement.as_names.append(n) statement.as_names.append(n)
+130 -107
View File
@@ -37,6 +37,7 @@ See also :attr:`Scope.subscopes` and :attr:`Scope.statements`.
import os import os
import re import re
from inspect import cleandoc from inspect import cleandoc
from collections import defaultdict
from jedi._compatibility import (next, Python3Method, encoding, unicode, from jedi._compatibility import (next, Python3Method, encoding, unicode,
is_py3, u, literal_eval, use_metaclass) is_py3, u, literal_eval, use_metaclass)
@@ -231,6 +232,14 @@ class IsScope(use_metaclass(IsScopeMeta)):
pass pass
def _return_empty_list():
"""
Necessary for pickling. It needs to be reachable for pickle, cannot
be a lambda or a closure.
"""
return []
class Scope(Simple, DocstringMixin): class Scope(Simple, DocstringMixin):
""" """
Super class for the parser tree, which represents the state of a python Super class for the parser tree, which represents the state of a python
@@ -243,7 +252,7 @@ class Scope(Simple, DocstringMixin):
:type start_pos: tuple(int, int) :type start_pos: tuple(int, int)
""" """
__slots__ = ('subscopes', 'imports', 'statements', '_doc_token', 'asserts', __slots__ = ('subscopes', 'imports', 'statements', '_doc_token', 'asserts',
'returns', 'is_generator') 'returns', 'is_generator', '_names_dict')
def __init__(self, module, start_pos): def __init__(self, module, start_pos):
super(Scope, self).__init__(module, start_pos) super(Scope, self).__init__(module, start_pos)
@@ -255,11 +264,19 @@ class Scope(Simple, DocstringMixin):
# Needed here for fast_parser, because the fast_parser splits and # Needed here for fast_parser, because the fast_parser splits and
# returns will be in "normal" modules. # returns will be in "normal" modules.
self.returns = [] self.returns = []
self._names_dict = defaultdict(_return_empty_list)
self.is_generator = False self.is_generator = False
def is_scope(self): def is_scope(self):
return True return True
def add_name_calls(self, name, calls):
"""Add a name to the names_dict."""
self._names_dict[name] += calls
def get_names_dict(self):
return self._names_dict
def add_scope(self, sub, decorators): def add_scope(self, sub, decorators):
sub.parent = self.use_as_parent sub.parent = self.use_as_parent
sub.decorators = decorators sub.decorators = decorators
@@ -468,8 +485,7 @@ class SubModule(Scope, Module):
string = re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1)) string = re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
# Positions are not real, but a module starts at (1, 0) # Positions are not real, but a module starts at (1, 0)
p = (1, 0) p = (1, 0)
names = [(string, p)] return Name(self, string, self.use_as_parent, p)
return Name(self, names, p, p, self.use_as_parent)
@property @property
def has_explicit_absolute_import(self): def has_explicit_absolute_import(self):
@@ -478,10 +494,10 @@ class SubModule(Scope, Module):
is a ``__future__`` import. is a ``__future__`` import.
""" """
for imp in self.imports: for imp in self.imports:
if imp.from_ns is None or imp.namespace is None: if not imp.from_names or not imp.namespace_names:
continue continue
namespace, feature = imp.from_ns.names[0], imp.namespace.names[0] namespace, feature = imp.from_names[0], imp.namespace_names[0]
if unicode(namespace) == "__future__" and unicode(feature) == "absolute_import": if unicode(namespace) == "__future__" and unicode(feature) == "absolute_import":
return True return True
@@ -529,9 +545,9 @@ class Class(Scope):
if self._doc_token is not None: if self._doc_token is not None:
docstr = self.raw_doc docstr = self.raw_doc
for sub in self.subscopes: for sub in self.subscopes:
if unicode(sub.name.names[-1]) == '__init__': if unicode(sub.name) == '__init__':
return '%s\n\n%s' % ( return '%s\n\n%s' % (
sub.get_call_signature(funcname=self.name.names[-1]), docstr) sub.get_call_signature(funcname=self.name), docstr)
return docstr return docstr
def scope_names_generator(self, position=None): def scope_names_generator(self, position=None):
@@ -597,7 +613,7 @@ class Function(Scope):
:rtype: str :rtype: str
""" """
l = unicode(funcname or self.name.names[-1]) + '(' l = unicode(funcname or self.name) + '('
lines = [] lines = []
for (i, p) in enumerate(self.params): for (i, p) in enumerate(self.params):
code = p.get_code(False) code = p.get_code(False)
@@ -672,6 +688,15 @@ class Flow(Scope):
s.parent = self.use_as_parent s.parent = self.use_as_parent
self.set_vars = [] self.set_vars = []
def add_name_calls(self, name, calls):
"""Add a name to the names_dict."""
parent = self.parent
if isinstance(parent, Module):
# TODO this also looks like code smell. Look for opportunities to
# remove.
parent = self._sub_module
parent.add_name_calls(name, calls)
@property @property
def parent(self): def parent(self):
return self._parent return self._parent
@@ -769,27 +794,28 @@ class Import(Simple):
:param start_pos: Position (line, column) of the Import. :param start_pos: Position (line, column) of the Import.
:type start_pos: tuple(int, int) :type start_pos: tuple(int, int)
:param namespace: The import, can be empty if a star is given :param namespace_names: The import, can be empty if a star is given
:type namespace: Name :type namespace_names: list of Name
:param alias: The alias of a namespace(valid in the current namespace). :param alias: The alias of a namespace(valid in the current namespace).
:type alias: Name :type alias: list of Name
:param from_ns: Like the namespace, can be equally used. :param from_names: Like the namespace, can be equally used.
:type from_ns: Name :type from_names: list of Name
:param star: If a star is used -> from time import *. :param star: If a star is used -> from time import *.
:type star: bool :type star: bool
:param defunct: An Import is valid or not. :param defunct: An Import is valid or not.
:type defunct: bool :type defunct: bool
""" """
def __init__(self, module, start_pos, end_pos, namespace, alias=None, def __init__(self, module, start_pos, end_pos, namespace_names, alias=None,
from_ns=None, star=False, relative_count=0, defunct=False): from_names=(), star=False, relative_count=0, defunct=False):
super(Import, self).__init__(module, start_pos, end_pos) super(Import, self).__init__(module, start_pos, end_pos)
self.namespace = namespace self.namespace_names = namespace_names
self.alias = alias self.alias = alias
self.from_ns = from_ns if self.alias:
for n in namespace, alias, from_ns: alias.parent = self
if n: self.from_names = from_names
n.parent = self.use_as_parent for n in namespace_names + list(from_names):
n.parent = self.use_as_parent
self.star = star self.star = star
self.relative_count = relative_count self.relative_count = relative_count
@@ -798,20 +824,18 @@ class Import(Simple):
def get_code(self, new_line=True): def get_code(self, new_line=True):
# in case one of the names is None # in case one of the names is None
alias = self.alias or '' alias = self.alias or ''
namespace = self.namespace or ''
from_ns = self.from_ns or ''
ns_str = '.'.join(unicode(n) for n in self.namespace_names)
if self.alias: if self.alias:
ns_str = "%s as %s" % (namespace, alias) ns_str = "%s as %s" % (ns_str, alias)
else:
ns_str = unicode(namespace)
nl = '\n' if new_line else '' nl = '\n' if new_line else ''
if self.from_ns or self.relative_count: if self.from_names or self.relative_count:
if self.star: if self.star:
ns_str = '*' ns_str = '*'
dots = '.' * self.relative_count dots = '.' * self.relative_count
return "from %s%s import %s%s" % (dots, from_ns, ns_str, nl) from_txt = '.'.join(unicode(n) for n in self.from_names)
return "from %s%s import %s%s" % (dots, from_txt, ns_str, nl)
else: else:
return "import %s%s" % (ns_str, nl) return "import %s%s" % (ns_str, nl)
@@ -822,21 +846,18 @@ class Import(Simple):
return [self] return [self]
if self.alias: if self.alias:
return [self.alias] return [self.alias]
if len(self.namespace) > 1: if len(self.namespace_names) > 1:
o = self.namespace return [self.namespace_names[0]]
n = Name(self._sub_module, [(unicode(o.names[0]), o.start_pos)],
o.start_pos, o.end_pos, parent=o.parent)
return [n]
else: else:
return [self.namespace] return self.namespace_names
def get_all_import_names(self): def get_all_import_names(self):
n = [] n = []
if self.from_ns: if self.from_names:
n.append(self.from_ns) n += self.from_names
if self.namespace: if self.namespace_names:
n.append(self.namespace) n += self.namespace_names
if self.alias: if self.alias is not None:
n.append(self.alias) n.append(self.alias)
return n return n
@@ -847,8 +868,8 @@ class Import(Simple):
import foo.bar import foo.bar
""" """
return not self.alias and not self.from_ns and self.namespace is not None \ return not self.alias and not self.from_names \
and len(self.namespace.names) > 1 and len(self.namespace_names) > 1
class KeywordStatement(Base): class KeywordStatement(Base):
@@ -911,9 +932,6 @@ class Statement(Simple, DocstringMixin):
self._token_list = token_list self._token_list = token_list
self._names_are_set_vars = names_are_set_vars self._names_are_set_vars = names_are_set_vars
if set_name_parents: if set_name_parents:
for t in token_list:
if isinstance(t, Name):
t.parent = self.use_as_parent
for n in as_names: for n in as_names:
n.parent = self.use_as_parent n.parent = self.use_as_parent
self._doc_token = None self._doc_token = None
@@ -950,7 +968,7 @@ class Statement(Simple, DocstringMixin):
return code return code
def get_defined_names(self): def get_defined_names(self):
""" Get the names for the statement. """ """Get the names for the statement."""
if self._set_vars is None: if self._set_vars is None:
def search_calls(calls): def search_calls(calls):
@@ -959,15 +977,11 @@ class Statement(Simple, DocstringMixin):
for stmt in call: for stmt in call:
search_calls(stmt.expression_list()) search_calls(stmt.expression_list())
elif isinstance(call, Call): elif isinstance(call, Call):
c = call
# Check if there's an execution in it, if so this is # Check if there's an execution in it, if so this is
# not a set_var. # not a set_var.
while c: if not call.next:
if isinstance(c.next, Array):
break
c = c.next
else:
self._set_vars.append(call.name) self._set_vars.append(call.name)
continue
self._set_vars = [] self._set_vars = []
for calls, operation in self.assignment_details: for calls, operation in self.assignment_details:
@@ -978,6 +992,37 @@ class Statement(Simple, DocstringMixin):
search_calls(self.expression_list()) search_calls(self.expression_list())
return self._set_vars + self.as_names return self._set_vars + self.as_names
def get_names_dict(self):
"""The future of name resolution. Returns a dict(str -> Call)."""
dct = defaultdict(lambda: [])
def search_calls(calls):
for call in calls:
if isinstance(call, Array) and call.type != Array.DICT:
for stmt in call:
search_calls(stmt.expression_list())
elif isinstance(call, Call):
c = call
# Check if there's an execution in it, if so this is
# not a set_var.
while True:
if c.next is None or isinstance(c.next, Array):
break
c = c.next
dct[unicode(c.name)].append(call)
for calls, operation in self.assignment_details:
search_calls(calls)
if not self.assignment_details and self._names_are_set_vars:
# In the case of Param, it's also a defining name without ``=``
search_calls(self.expression_list())
for as_name in self.as_names:
dct[unicode(as_name)].append(Call(self._sub_module, as_name,
as_name.start_pos, as_name.end_pos, self))
return dct
def is_global(self): def is_global(self):
p = self.parent p = self.parent
return isinstance(p, KeywordStatement) and p.name == 'global' return isinstance(p, KeywordStatement) and p.name == 'global'
@@ -1050,7 +1095,7 @@ class Statement(Simple, DocstringMixin):
return arr, break_tok return arr, break_tok
def parse_stmt(token_iterator, maybe_dict=False, added_breaks=(), def parse_stmt(token_iterator, maybe_dict=False, added_breaks=(),
break_on_assignment=False, stmt_class=Statement, break_on_assignment=False, stmt_class=ArrayStmt,
allow_comma=False): allow_comma=False):
token_list = [] token_list = []
level = 0 level = 0
@@ -1064,7 +1109,7 @@ class Statement(Simple, DocstringMixin):
first = False first = False
if isinstance(tok, Base): if isinstance(tok, Base):
# the token is a Name, which has already been parsed # The token is a Name, which has already been parsed.
if not level: if not level:
if isinstance(tok, ListComprehension): if isinstance(tok, ListComprehension):
# it's not possible to set it earlier # it's not possible to set it earlier
@@ -1134,13 +1179,10 @@ class Statement(Simple, DocstringMixin):
added_breaks=added_breaks) added_breaks=added_breaks)
if stmt is not None: if stmt is not None:
for t in stmt._token_list:
if isinstance(t, Name):
t.parent = stmt
stmt._names_are_set_vars = names_are_set_vars stmt._names_are_set_vars = names_are_set_vars
return stmt, tok return stmt, tok
st = Statement(self._sub_module, token_list, start_pos, st = ArrayStmt(self._sub_module, token_list, start_pos,
end_pos, set_name_parents=False) end_pos, set_name_parents=False)
middle, tok = parse_stmt_or_arr(token_iterator, ['in'], True) middle, tok = parse_stmt_or_arr(token_iterator, ['in'], True)
@@ -1170,7 +1212,7 @@ class Statement(Simple, DocstringMixin):
next(token_iterator, None) next(token_iterator, None)
continue continue
else: else:
# the token is a Name, which has already been parsed # The token is a Name, which has already been parsed
tok_str = tok tok_str = tok
token_type = None token_type = None
@@ -1190,7 +1232,7 @@ class Statement(Simple, DocstringMixin):
continue continue
is_literal = token_type in (tokenize.STRING, tokenize.NUMBER) is_literal = token_type in (tokenize.STRING, tokenize.NUMBER)
if isinstance(tok_str, Name) or is_literal: if is_literal or isinstance(tok, Name):
cls = Literal if is_literal else Call cls = Literal if is_literal else Call
call = cls(self._sub_module, tok_str, tok.start_pos, tok.end_pos, self) call = cls(self._sub_module, tok_str, tok.start_pos, tok.end_pos, self)
@@ -1213,7 +1255,7 @@ class Statement(Simple, DocstringMixin):
is_chain = True is_chain = True
elif tok_str == ',' and result: # implies a tuple elif tok_str == ',' and result: # implies a tuple
# expression is now an array not a statement anymore # expression is now an array not a statement anymore
stmt = Statement(self._sub_module, result, result[0].start_pos, stmt = ArrayStmt(self._sub_module, result, result[0].start_pos,
tok.end_pos, self.parent, set_name_parents=False) tok.end_pos, self.parent, set_name_parents=False)
stmt._expression_list = result stmt._expression_list = result
arr, break_tok = parse_array(token_iterator, Array.TUPLE, arr, break_tok = parse_array(token_iterator, Array.TUPLE,
@@ -1247,6 +1289,14 @@ class ExprStmt(Statement):
""" """
class ArrayStmt(Statement):
"""
This class exists temporarily. Like ``ExprStatement``, this exists to
distinguish between real statements and stuff that is defined in those
statements.
"""
class Param(ExprStmt): class Param(ExprStmt):
""" """
The class which shows definitions of params of classes and functions. The class which shows definitions of params of classes and functions.
@@ -1308,8 +1358,7 @@ class StatementElement(Simple):
def generate_call_path(self): def generate_call_path(self):
""" Helps to get the order in which statements are executed. """ """ Helps to get the order in which statements are executed. """
try: try:
for name_part in self.name.names: yield self.name
yield name_part
except AttributeError: except AttributeError:
yield self yield self
if self.next is not None: if self.next is not None:
@@ -1318,7 +1367,7 @@ class StatementElement(Simple):
def get_code(self): def get_code(self):
if self.next is not None: if self.next is not None:
s = '.' if isinstance(self, Array) else '' s = '.' if not isinstance(self.next, Array) else ''
return s + self.next.get_code() return s + self.next.get_code()
return '' return ''
@@ -1334,6 +1383,21 @@ class Call(StatementElement):
def get_code(self): def get_code(self):
return self.name.get_code() + super(Call, self).get_code() return self.name.get_code() + super(Call, self).get_code()
def names(self):
"""
Generate an array of string names. If a call is not just names,
raise an error.
"""
def check(call):
while call is not None:
if not isinstance(call, Call): # Could be an Array.
break
yield unicode(call.name)
call = call.next
return list(check(self))
def __repr__(self): def __repr__(self):
return "<%s: %s>" % (type(self).__name__, self.name) return "<%s: %s>" % (type(self).__name__, self.name)
@@ -1450,7 +1514,7 @@ class Array(StatementElement):
return "<%s: %s%s>" % (type(self).__name__, typ, self.values) return "<%s: %s%s>" % (type(self).__name__, typ, self.values)
class NamePart(object): class Name(object):
""" """
A string. Sometimes it is important to know if the string belongs to a name A string. Sometimes it is important to know if the string belongs to a name
or not. or not.
@@ -1474,13 +1538,14 @@ class NamePart(object):
return self._string return self._string
def __repr__(self): def __repr__(self):
return "<%s: %s>" % (type(self).__name__, self._string) return "<%s: %s@%s,%s>" % (type(self).__name__, self._string,
self.start_pos[0], self.start_pos[1])
def get_code(self): def get_code(self):
return self._string return self._string
def get_definition(self): def get_definition(self):
return self.parent.get_definition() return self.get_parent_until((ArrayStmt, StatementElement), reverse=True)
def get_parent_until(self, *args, **kwargs): def get_parent_until(self, *args, **kwargs):
return self.parent.get_parent_until(*args, **kwargs) return self.parent.get_parent_until(*args, **kwargs)
@@ -1498,48 +1563,6 @@ class NamePart(object):
return self.start_pos[0], self.start_pos[1] + len(self._string) return self.start_pos[0], self.start_pos[1] + len(self._string)
class Name(Simple):
"""
Used to define names in python.
Which means the whole namespace/class/function stuff.
So a name like "module.class.function"
would result in an array of [module, class, function]
"""
__slots__ = ('names', '_get_code')
def __init__(self, module, names, start_pos, end_pos, parent=None):
super(Name, self).__init__(module, start_pos, end_pos, parent)
# Cache get_code, because it's used quite often for comparisons
# (seen by using the profiler).
self._get_code = ".".join(n[0] for n in names)
names = tuple(NamePart(module, n[0], self, n[1]) for n in names)
self.names = names
def get_code(self):
""" Returns the names in a full string format """
return self._get_code
def get_definition(self):
# TODO This is way to complicated, simplify this with a new parser.
return self.get_parent_until((ExprStmt, IsScope, Import))
@property
def end_pos(self):
return self.names[-1].end_pos
@property
def docstr(self):
"""Return attribute docstring (PEP 257) if exists."""
return self.parent.docstr
def __str__(self):
return self.get_code()
def __len__(self):
return len(self.names)
class ListComprehension(ForFlow): class ListComprehension(ForFlow):
""" Helper class for list comprehensions """ """ Helper class for list comprehensions """
def __init__(self, module, stmt, middle, input, parent): def __init__(self, module, stmt, middle, input, parent):
+3 -1
View File
@@ -2,7 +2,9 @@
This is used for dynamic object completion. This is used for dynamic object completion.
Jedi tries to guess the types with a backtracking approach. Jedi tries to guess the types with a backtracking approach.
""" """
def func(a): def func(a, default_arg=2):
#? int()
default_arg
#? int() str() #? int() str()
return a return a
+1 -1
View File
@@ -185,7 +185,7 @@ class TestClass(Super):
TestClass.base_var TestClass.base_var
#< 13 (5,13), (0,13) #< 13 (5,13), (0,13), (-24,13)
self.instance_var = 3 self.instance_var = 3
#< 9 (0,8), #< 9 (0,8),
+79
View File
@@ -148,6 +148,28 @@ def test_signature_params():
check(Script(s + '\nbar=foo\nbar').goto_assignments()) check(Script(s + '\nbar=foo\nbar').goto_assignments())
class TestIsDefinition(TestCase):
def _def(self, source, index=-1):
return names(dedent(source), references=True, all_scopes=True)[index]
def test_name(self):
d = self._def('name')
assert d.name == 'name'
assert not d.is_definition()
def test_stmt(self):
src = 'a = f(x)'
d = self._def(src, 0)
assert d.name == 'a'
assert d.is_definition()
d = self._def(src, 1)
assert d.name == 'f'
assert not d.is_definition()
d = self._def(src)
assert d.name == 'x'
assert not d.is_definition()
class TestParent(TestCase): class TestParent(TestCase):
def _parent(self, source, line=None, column=None): def _parent(self, source, line=None, column=None):
defs = Script(dedent(source), line, column).goto_assignments() defs = Script(dedent(source), line, column).goto_assignments()
@@ -219,3 +241,60 @@ class TestGotoAssignments(TestCase):
param = bar.goto_assignments()[0] param = bar.goto_assignments()[0]
assert param.start_pos == (1, 13) assert param.start_pos == (1, 13)
assert param.type == 'param' assert param.type == 'param'
def test_class_call(self):
src = 'from threading import Thread; Thread(group=1)'
n = names(src, references=True)[-1]
assert n.name == 'group'
param_def = n.goto_assignments()[0]
assert param_def.name == 'group'
assert param_def.type == 'param'
def test_parentheses(self):
n = names('("").upper', references=True)[-1]
assert n.goto_assignments()[0].name == 'upper'
def test_import(self):
nms = names('from json import load', references=True)
assert nms[0].name == 'json'
assert nms[0].type == 'import'
n = nms[0].goto_assignments()[0]
assert n.name == 'json'
assert n.type == 'module'
assert nms[1].name == 'load'
assert nms[1].type == 'import'
n = nms[1].goto_assignments()[0]
assert n.name == 'load'
assert n.type == 'function'
nms = names('import os; os.path', references=True)
assert nms[0].name == 'os'
assert nms[0].type == 'import'
n = nms[0].goto_assignments()[0]
assert n.name == 'os'
assert n.type == 'module'
n = nms[2].goto_assignments()[0]
assert n.name == 'path'
assert n.type == 'import'
nms = names('import os.path', references=True)
n = nms[0].goto_assignments()[0]
assert n.name == 'os'
assert n.type == 'module'
n = nms[1].goto_assignments()[0]
assert n.name == 'path'
assert n.type == 'import'
def test_import_alias(self):
nms = names('import json as foo', references=True)
assert nms[0].name == 'json'
assert nms[0].type == 'import'
n = nms[0].goto_assignments()[0]
assert n.name == 'json'
assert n.type == 'module'
assert nms[1].name == 'foo'
assert nms[1].type == 'import'
assert [nms[1]] == nms[1].goto_assignments()
-9
View File
@@ -1,18 +1,9 @@
from jedi._compatibility import unicode from jedi._compatibility import unicode
from jedi.evaluate import helpers from jedi.evaluate import helpers
from jedi.parser import representation as pr
from jedi.parser import Parser from jedi.parser import Parser
def test_deep_ast_copy():
name = pr.Name(object, [('hallo', (0, 0))], (0, 0), (0, 0))
# fast parent copy should switch parent
new_name = helpers.deep_ast_copy(name)
assert new_name.names[0].parent == new_name
def test_statement_elements_in_statement(): def test_statement_elements_in_statement():
def get_stmt_els(string): def get_stmt_els(string):
p = Parser(unicode(string)) p = Parser(unicode(string))
+1 -1
View File
@@ -5,7 +5,7 @@ from jedi import Script
def get_definition_and_evaluator(source): def get_definition_and_evaluator(source):
d = Script(dedent(source)).goto_definitions()[0] d = Script(dedent(source)).goto_definitions()[0]
return d._name.parent.parent, d._evaluator return d._name.parent, d._evaluator
def test_function_execution(): def test_function_execution():
+1 -1
View File
@@ -26,4 +26,4 @@ asdfasdf""" + "h"
def test_tokenizer_with_string_literal_backslash(): def test_tokenizer_with_string_literal_backslash():
import jedi import jedi
c = jedi.Script("statement = u'foo\\\n'; statement").goto_definitions() c = jedi.Script("statement = u'foo\\\n'; statement").goto_definitions()
assert c[0]._name.parent.parent.obj == 'foo' assert c[0]._name.parent.obj == 'foo'