forked from VimPlug/jedi
Move the evaluate package to inference
This commit is contained in:
269
jedi/inference/helpers.py
Normal file
269
jedi/inference/helpers.py
Normal file
@@ -0,0 +1,269 @@
|
||||
import copy
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
from itertools import chain
|
||||
from contextlib import contextmanager
|
||||
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import unicode
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
def is_stdlib_path(path):
|
||||
# Python standard library paths look like this:
|
||||
# /usr/lib/python3.5/...
|
||||
# TODO The implementation below is probably incorrect and not complete.
|
||||
if 'dist-packages' in path or 'site-packages' in path:
|
||||
return False
|
||||
|
||||
base_path = os.path.join(sys.prefix, 'lib', 'python')
|
||||
return bool(re.match(re.escape(base_path) + r'\d.\d', path))
|
||||
|
||||
|
||||
def deep_ast_copy(obj):
|
||||
"""
|
||||
Much, much faster than copy.deepcopy, but just for parser tree nodes.
|
||||
"""
|
||||
# If it's already in the cache, just return it.
|
||||
new_obj = copy.copy(obj)
|
||||
|
||||
# Copy children
|
||||
new_children = []
|
||||
for child in obj.children:
|
||||
if isinstance(child, tree.Leaf):
|
||||
new_child = copy.copy(child)
|
||||
new_child.parent = new_obj
|
||||
else:
|
||||
new_child = deep_ast_copy(child)
|
||||
new_child.parent = new_obj
|
||||
new_children.append(new_child)
|
||||
new_obj.children = new_children
|
||||
|
||||
return new_obj
|
||||
|
||||
|
||||
def infer_call_of_leaf(context, leaf, cut_own_trailer=False):
|
||||
"""
|
||||
Creates a "call" node that consist of all ``trailer`` and ``power``
|
||||
objects. E.g. if you call it with ``append``::
|
||||
|
||||
list([]).append(3) or None
|
||||
|
||||
You would get a node with the content ``list([]).append`` back.
|
||||
|
||||
This generates a copy of the original ast node.
|
||||
|
||||
If you're using the leaf, e.g. the bracket `)` it will return ``list([])``.
|
||||
|
||||
We use this function for two purposes. Given an expression ``bar.foo``,
|
||||
we may want to
|
||||
- infer the type of ``foo`` to offer completions after foo
|
||||
- infer the type of ``bar`` to be able to jump to the definition of foo
|
||||
The option ``cut_own_trailer`` must be set to true for the second purpose.
|
||||
"""
|
||||
trailer = leaf.parent
|
||||
if trailer.type == 'fstring':
|
||||
from jedi.inference import compiled
|
||||
return compiled.get_string_context_set(context.evaluator)
|
||||
|
||||
# The leaf may not be the last or first child, because there exist three
|
||||
# different trailers: `( x )`, `[ x ]` and `.x`. In the first two examples
|
||||
# we should not match anything more than x.
|
||||
if trailer.type != 'trailer' or leaf not in (trailer.children[0], trailer.children[-1]):
|
||||
if trailer.type == 'atom':
|
||||
return context.eval_node(trailer)
|
||||
return context.eval_node(leaf)
|
||||
|
||||
power = trailer.parent
|
||||
index = power.children.index(trailer)
|
||||
if cut_own_trailer:
|
||||
cut = index
|
||||
else:
|
||||
cut = index + 1
|
||||
|
||||
if power.type == 'error_node':
|
||||
start = index
|
||||
while True:
|
||||
start -= 1
|
||||
base = power.children[start]
|
||||
if base.type != 'trailer':
|
||||
break
|
||||
trailers = power.children[start + 1: index + 1]
|
||||
else:
|
||||
base = power.children[0]
|
||||
trailers = power.children[1:cut]
|
||||
|
||||
if base == 'await':
|
||||
base = trailers[0]
|
||||
trailers = trailers[1:]
|
||||
|
||||
values = context.eval_node(base)
|
||||
from jedi.inference.syntax_tree import eval_trailer
|
||||
for trailer in trailers:
|
||||
values = eval_trailer(context, values, trailer)
|
||||
return values
|
||||
|
||||
|
||||
def call_of_leaf(leaf):
|
||||
"""
|
||||
Creates a "call" node that consist of all ``trailer`` and ``power``
|
||||
objects. E.g. if you call it with ``append``::
|
||||
|
||||
list([]).append(3) or None
|
||||
|
||||
You would get a node with the content ``list([]).append`` back.
|
||||
|
||||
This generates a copy of the original ast node.
|
||||
|
||||
If you're using the leaf, e.g. the bracket `)` it will return ``list([])``.
|
||||
"""
|
||||
# TODO this is the old version of this call. Try to remove it.
|
||||
trailer = leaf.parent
|
||||
# The leaf may not be the last or first child, because there exist three
|
||||
# different trailers: `( x )`, `[ x ]` and `.x`. In the first two examples
|
||||
# we should not match anything more than x.
|
||||
if trailer.type != 'trailer' or leaf not in (trailer.children[0], trailer.children[-1]):
|
||||
if trailer.type == 'atom':
|
||||
return trailer
|
||||
return leaf
|
||||
|
||||
power = trailer.parent
|
||||
index = power.children.index(trailer)
|
||||
|
||||
new_power = copy.copy(power)
|
||||
new_power.children = list(new_power.children)
|
||||
new_power.children[index + 1:] = []
|
||||
|
||||
if power.type == 'error_node':
|
||||
start = index
|
||||
while True:
|
||||
start -= 1
|
||||
if power.children[start].type != 'trailer':
|
||||
break
|
||||
transformed = tree.Node('power', power.children[start:])
|
||||
transformed.parent = power.parent
|
||||
return transformed
|
||||
|
||||
return power
|
||||
|
||||
|
||||
def get_names_of_node(node):
|
||||
try:
|
||||
children = node.children
|
||||
except AttributeError:
|
||||
if node.type == 'name':
|
||||
return [node]
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
return list(chain.from_iterable(get_names_of_node(c) for c in children))
|
||||
|
||||
|
||||
def get_module_names(module, all_scopes):
|
||||
"""
|
||||
Returns a dictionary with name parts as keys and their call paths as
|
||||
values.
|
||||
"""
|
||||
names = list(chain.from_iterable(module.get_used_names().values()))
|
||||
if not all_scopes:
|
||||
# We have to filter all the names that don't have the module as a
|
||||
# parent_scope. There's None as a parent, because nodes in the module
|
||||
# node have the parent module and not suite as all the others.
|
||||
# Therefore it's important to catch that case.
|
||||
|
||||
def is_module_scope_name(name):
|
||||
parent_scope = get_parent_scope(name)
|
||||
# async functions have an extra wrapper. Strip it.
|
||||
if parent_scope and parent_scope.type == 'async_stmt':
|
||||
parent_scope = parent_scope.parent
|
||||
return parent_scope in (module, None)
|
||||
|
||||
names = [n for n in names if is_module_scope_name(n)]
|
||||
return names
|
||||
|
||||
|
||||
@contextmanager
|
||||
def predefine_names(context, flow_scope, dct):
|
||||
predefined = context.predefined_names
|
||||
predefined[flow_scope] = dct
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
del predefined[flow_scope]
|
||||
|
||||
|
||||
def is_string(context):
|
||||
if context.evaluator.environment.version_info.major == 2:
|
||||
str_classes = (unicode, bytes)
|
||||
else:
|
||||
str_classes = (unicode,)
|
||||
return context.is_compiled() and isinstance(context.get_safe_value(default=None), str_classes)
|
||||
|
||||
|
||||
def is_literal(context):
|
||||
return is_number(context) or is_string(context)
|
||||
|
||||
|
||||
def _get_safe_value_or_none(context, accept):
|
||||
value = context.get_safe_value(default=None)
|
||||
if isinstance(value, accept):
|
||||
return value
|
||||
|
||||
|
||||
def get_int_or_none(context):
|
||||
return _get_safe_value_or_none(context, int)
|
||||
|
||||
|
||||
def get_str_or_none(context):
|
||||
return _get_safe_value_or_none(context, (bytes, unicode))
|
||||
|
||||
|
||||
def is_number(context):
|
||||
return _get_safe_value_or_none(context, (int, float)) is not None
|
||||
|
||||
|
||||
class SimpleGetItemNotFound(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def reraise_getitem_errors(*exception_classes):
|
||||
try:
|
||||
yield
|
||||
except exception_classes as e:
|
||||
raise SimpleGetItemNotFound(e)
|
||||
|
||||
|
||||
def parse_dotted_names(nodes, is_import_from, until_node=None):
|
||||
level = 0
|
||||
names = []
|
||||
for node in nodes[1:]:
|
||||
if node in ('.', '...'):
|
||||
if not names:
|
||||
level += len(node.value)
|
||||
elif node.type == 'dotted_name':
|
||||
for n in node.children[::2]:
|
||||
names.append(n)
|
||||
if n is until_node:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
elif node.type == 'name':
|
||||
names.append(node)
|
||||
if node is until_node:
|
||||
break
|
||||
elif node == ',':
|
||||
if not is_import_from:
|
||||
names = []
|
||||
else:
|
||||
# Here if the keyword `import` comes along it stops checking
|
||||
# for names.
|
||||
break
|
||||
return level, names
|
||||
|
||||
|
||||
def contexts_from_qualified_names(evaluator, *names):
|
||||
return evaluator.import_module(names[:-1]).py__getattribute__(names[-1])
|
||||
Reference in New Issue
Block a user