1
0
forked from VimPlug/jedi

Remove the precedence module in favor of the syntax tree module.

This commit is contained in:
Dave Halter
2017-09-27 19:08:52 +02:00
parent d0939f0449
commit ed43a68c03
5 changed files with 159 additions and 164 deletions

View File

@@ -29,7 +29,7 @@ from jedi.evaluate import compiled
from jedi.evaluate import helpers from jedi.evaluate import helpers
from jedi.evaluate import analysis from jedi.evaluate import analysis
from jedi.evaluate import pep0484 from jedi.evaluate import pep0484
from jedi.evaluate import precedence from jedi.evaluate.syntax_tree import is_string
from jedi.evaluate import recursion from jedi.evaluate import recursion
from jedi.evaluate.cache import evaluator_method_cache from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.filters import DictFilter, AbstractNameDefinition, \ from jedi.evaluate.filters import DictFilter, AbstractNameDefinition, \
@@ -462,7 +462,7 @@ class SequenceLiteralContext(ArrayMixin, AbstractSequence):
""" """
for key_node, value in self._items(): for key_node, value in self._items():
for key in self._defining_context.eval_node(key_node): for key in self._defining_context.eval_node(key_node):
if precedence.is_string(key): if is_string(key):
yield key.obj, context.LazyTreeContext(self._defining_context, value) yield key.obj, context.LazyTreeContext(self._defining_context, value)
def __repr__(self): def __repr__(self):

View File

@@ -1,151 +0,0 @@
"""
Handles operator precedence.
"""
import operator as op
from jedi._compatibility import unicode
from jedi.evaluate.compiled import CompiledObject, create, builtin_from_name
from jedi.evaluate import analysis
from jedi.evaluate.context import ContextSet, NO_CONTEXTS, iterator_to_context_set
# Maps Python syntax to the operator module.
COMPARISON_OPERATORS = {
'==': op.eq,
'!=': op.ne,
'is': op.is_,
'is not': op.is_not,
'<': op.lt,
'<=': op.le,
'>': op.gt,
'>=': op.ge,
}
def literals_to_types(evaluator, result):
# Changes literals ('a', 1, 1.0, etc) to its type instances (str(),
# int(), float(), etc).
new_result = NO_CONTEXTS
for typ in result:
if is_literal(typ):
# Literals are only valid as long as the operations are
# correct. Otherwise add a value-free instance.
cls = builtin_from_name(evaluator, typ.name.string_name)
new_result |= cls.execute_evaluated()
else:
new_result |= ContextSet(typ)
return new_result
def calculate(evaluator, context, left_contexts, operator, right_contexts):
if not left_contexts or not right_contexts:
# illegal slices e.g. cause left/right_result to be None
result = (left_contexts or NO_CONTEXTS) | (right_contexts or NO_CONTEXTS)
return literals_to_types(evaluator, result)
else:
# I don't think there's a reasonable chance that a string
# operation is still correct, once we pass something like six
# objects.
if len(left_contexts) * len(right_contexts) > 6:
return literals_to_types(evaluator, left_contexts | right_contexts)
else:
return ContextSet.from_sets(
_element_calculate(evaluator, context, left, operator, right)
for left in left_contexts
for right in right_contexts
)
@iterator_to_context_set
def factor_calculate(evaluator, types, operator):
"""
Calculates `+`, `-`, `~` and `not` prefixes.
"""
for typ in types:
if operator == '-':
if _is_number(typ):
yield create(evaluator, -typ.obj)
elif operator == 'not':
value = typ.py__bool__()
if value is None: # Uncertainty.
return
yield create(evaluator, not value)
else:
yield typ
def _is_number(obj):
return isinstance(obj, CompiledObject) \
and isinstance(obj.obj, (int, float))
def is_string(obj):
return isinstance(obj, CompiledObject) \
and isinstance(obj.obj, (str, unicode))
def is_literal(obj):
return _is_number(obj) or is_string(obj)
def _is_tuple(obj):
from jedi.evaluate import iterable
return isinstance(obj, iterable.AbstractSequence) and obj.array_type == 'tuple'
def _is_list(obj):
from jedi.evaluate import iterable
return isinstance(obj, iterable.AbstractSequence) and obj.array_type == 'list'
def _element_calculate(evaluator, context, left, operator, right):
from jedi.evaluate import iterable, instance
l_is_num = _is_number(left)
r_is_num = _is_number(right)
if operator == '*':
# for iterables, ignore * operations
if isinstance(left, iterable.AbstractSequence) or is_string(left):
return ContextSet(left)
elif isinstance(right, iterable.AbstractSequence) or is_string(right):
return ContextSet(right)
elif operator == '+':
if l_is_num and r_is_num or is_string(left) and is_string(right):
return ContextSet(create(evaluator, left.obj + right.obj))
elif _is_tuple(left) and _is_tuple(right) or _is_list(left) and _is_list(right):
return ContextSet(iterable.MergedArray(evaluator, (left, right)))
elif operator == '-':
if l_is_num and r_is_num:
return ContextSet(create(evaluator, left.obj - right.obj))
elif operator == '%':
# With strings and numbers the left type typically remains. Except for
# `int() % float()`.
return ContextSet(left)
elif operator in COMPARISON_OPERATORS:
operation = COMPARISON_OPERATORS[operator]
if isinstance(left, CompiledObject) and isinstance(right, CompiledObject):
# Possible, because the return is not an option. Just compare.
left = left.obj
right = right.obj
try:
result = operation(left, right)
except TypeError:
# Could be True or False.
return ContextSet(create(evaluator, True), create(evaluator, False))
else:
return ContextSet(create(evaluator, result))
elif operator == 'in':
return NO_CONTEXTS
def check(obj):
"""Checks if a Jedi object is either a float or an int."""
return isinstance(obj, instance.CompiledInstance) and \
obj.name.string_name in ('int', 'float')
# Static analysis, one is a number, the other one is not.
if operator in ('+', '-') and l_is_num != r_is_num \
and not (check(left) or check(right)):
message = "TypeError: unsupported operand type(s) for +: %s and %s"
analysis.add(context, 'type-error-operation', operator,
message % (left, right))
return ContextSet(left, right)

View File

@@ -20,11 +20,11 @@ from jedi.evaluate.instance import InstanceFunctionExecution, \
AnonymousInstanceFunctionExecution AnonymousInstanceFunctionExecution
from jedi.evaluate import iterable from jedi.evaluate import iterable
from jedi import debug from jedi import debug
from jedi.evaluate import precedence
from jedi.evaluate import param from jedi.evaluate import param
from jedi.evaluate import analysis from jedi.evaluate import analysis
from jedi.evaluate.context import LazyTreeContext, ContextualizedNode, \ from jedi.evaluate.context import LazyTreeContext, ContextualizedNode, \
NO_CONTEXTS, ContextSet NO_CONTEXTS, ContextSet
from jedi.evaluate.syntax_tree import is_string
# Now this is all part of fake tuples in Jedi. However super doesn't work on # Now this is all part of fake tuples in Jedi. However super doesn't work on
# __init__ and __new__ doesn't work at all. So adding this to nametuples is # __init__ and __new__ doesn't work at all. So adding this to nametuples is
@@ -156,7 +156,7 @@ def builtins_getattr(evaluator, objects, names, defaults=None):
# follow the first param # follow the first param
for obj in objects: for obj in objects:
for name in names: for name in names:
if precedence.is_string(name): if is_string(name):
return obj.py__getattribute__(name.obj) return obj.py__getattribute__(name.obj)
else: else:
debug.warning('getattr called without str') debug.warning('getattr called without str')

View File

@@ -2,18 +2,19 @@
Functions evaluating the syntax tree. Functions evaluating the syntax tree.
""" """
import copy import copy
import operator as op
from parso.python import tree from parso.python import tree
from jedi import debug from jedi import debug
from jedi import parser_utils from jedi import parser_utils
from jedi.evaluate.context import ContextSet, NO_CONTEXTS, ContextualizedNode, \ from jedi.evaluate.context import ContextSet, NO_CONTEXTS, ContextualizedNode, \
ContextualizedName ContextualizedName, iterator_to_context_set
from jedi.evaluate import compiled from jedi.evaluate import compiled
from jedi.evaluate import precedence
from jedi.evaluate import pep0484 from jedi.evaluate import pep0484
from jedi.evaluate import recursion from jedi.evaluate import recursion
from jedi.evaluate import helpers from jedi.evaluate import helpers
from jedi.evaluate import analysis
def _limit_context_infers(func): def _limit_context_infers(func):
@@ -66,7 +67,7 @@ def eval_node(context, element):
for trailer in element.children[1:]: for trailer in element.children[1:]:
if trailer == '**': # has a power operation. if trailer == '**': # has a power operation.
right = evaluator.eval_element(context, element.children[2]) right = evaluator.eval_element(context, element.children[2])
context_set = precedence.calculate( context_set = calculate(
evaluator, evaluator,
context, context,
context_set, context_set,
@@ -84,7 +85,7 @@ def eval_node(context, element):
elif typ in ('not_test', 'factor'): elif typ in ('not_test', 'factor'):
context_set = context.eval_node(element.children[-1]) context_set = context.eval_node(element.children[-1])
for operator in element.children[:-1]: for operator in element.children[:-1]:
context_set = precedence.factor_calculate(evaluator, context_set, operator) context_set = eval_factor(context_set, operator)
return context_set return context_set
elif typ == 'test': elif typ == 'test':
# `x if foo else y` case. # `x if foo else y` case.
@@ -162,7 +163,7 @@ def eval_atom(context, atom):
context_set = eval_atom(context, c[0]) context_set = eval_atom(context, c[0])
for string in c[1:]: for string in c[1:]:
right = eval_atom(context, string) right = eval_atom(context, string)
context_set = precedence.calculate(context.evaluator, context, context_set, '+', right) context_set = calculate(context.evaluator, context, context_set, '+', right)
return context_set return context_set
# Parentheses without commas are not tuples. # Parentheses without commas are not tuples.
elif c[0] == '(' and not len(c) == 2 \ elif c[0] == '(' and not len(c) == 2 \
@@ -249,10 +250,10 @@ def _eval_expr_stmt(context, stmt, seek_name=None):
dct = {for_stmt.children[1].value: lazy_context.infer()} dct = {for_stmt.children[1].value: lazy_context.infer()}
with helpers.predefine_names(context, for_stmt, dct): with helpers.predefine_names(context, for_stmt, dct):
t = context.eval_node(rhs) t = context.eval_node(rhs)
left = precedence.calculate(context.evaluator, context, left, operator, t) left = calculate(context.evaluator, context, left, operator, t)
context_set = left context_set = left
else: else:
context_set = precedence.calculate(context.evaluator, context, left, operator, context_set) context_set = calculate(context.evaluator, context, left, operator, context_set)
debug.dbg('eval_expr_stmt result %s', context_set) debug.dbg('eval_expr_stmt result %s', context_set)
return context_set return context_set
@@ -276,7 +277,152 @@ def eval_or_test(context, or_test):
types = context.eval_node(right) types = context.eval_node(right)
# Otherwise continue, because of uncertainty. # Otherwise continue, because of uncertainty.
else: else:
types = precedence.calculate(context.evaluator, context, types, operator, types = calculate(context.evaluator, context, types, operator,
context.eval_node(right)) context.eval_node(right))
debug.dbg('calculate_children types %s', types) debug.dbg('calculate_children types %s', types)
return types return types
@iterator_to_context_set
def eval_factor(context_set, operator):
"""
Calculates `+`, `-`, `~` and `not` prefixes.
"""
for context in context_set:
if operator == '-':
if _is_number(context):
yield compiled.create(context.evaluator, -context.obj)
elif operator == 'not':
value = context.py__bool__()
if value is None: # Uncertainty.
return
yield compiled.create(context.evaluator, not value)
else:
yield context
# Maps Python syntax to the operator module.
COMPARISON_OPERATORS = {
'==': op.eq,
'!=': op.ne,
'is': op.is_,
'is not': op.is_not,
'<': op.lt,
'<=': op.le,
'>': op.gt,
'>=': op.ge,
}
def literals_to_types(evaluator, result):
# Changes literals ('a', 1, 1.0, etc) to its type instances (str(),
# int(), float(), etc).
new_result = NO_CONTEXTS
for typ in result:
if is_literal(typ):
# Literals are only valid as long as the operations are
# correct. Otherwise add a value-free instance.
cls = compiled.builtin_from_name(evaluator, typ.name.string_name)
new_result |= cls.execute_evaluated()
else:
new_result |= ContextSet(typ)
return new_result
def calculate(evaluator, context, left_contexts, operator, right_contexts):
if not left_contexts or not right_contexts:
# illegal slices e.g. cause left/right_result to be None
result = (left_contexts or NO_CONTEXTS) | (right_contexts or NO_CONTEXTS)
return literals_to_types(evaluator, result)
else:
# I don't think there's a reasonable chance that a string
# operation is still correct, once we pass something like six
# objects.
if len(left_contexts) * len(right_contexts) > 6:
return literals_to_types(evaluator, left_contexts | right_contexts)
else:
return ContextSet.from_sets(
_element_calculate(evaluator, context, left, operator, right)
for left in left_contexts
for right in right_contexts
)
def is_compiled(context):
return isinstance(context, compiled.CompiledObject)
def _is_number(context):
return is_compiled(context) and isinstance(context.obj, (int, float))
def is_string(context):
return is_compiled(context) and isinstance(context.obj, (str, unicode))
def is_literal(context):
return _is_number(context) or is_string(context)
def _is_tuple(context):
from jedi.evaluate import iterable
return isinstance(context, iterable.AbstractSequence) and context.array_type == 'tuple'
def _is_list(context):
from jedi.evaluate import iterable
return isinstance(context, iterable.AbstractSequence) and context.array_type == 'list'
def _element_calculate(evaluator, context, left, operator, right):
from jedi.evaluate import iterable, instance
l_is_num = _is_number(left)
r_is_num = _is_number(right)
if operator == '*':
# for iterables, ignore * operations
if isinstance(left, iterable.AbstractSequence) or is_string(left):
return ContextSet(left)
elif isinstance(right, iterable.AbstractSequence) or is_string(right):
return ContextSet(right)
elif operator == '+':
if l_is_num and r_is_num or is_string(left) and is_string(right):
return ContextSet(compiled.create(evaluator, left.obj + right.obj))
elif _is_tuple(left) and _is_tuple(right) or _is_list(left) and _is_list(right):
return ContextSet(iterable.MergedArray(evaluator, (left, right)))
elif operator == '-':
if l_is_num and r_is_num:
return ContextSet(compiled.create(evaluator, left.obj - right.obj))
elif operator == '%':
# With strings and numbers the left type typically remains. Except for
# `int() % float()`.
return ContextSet(left)
elif operator in COMPARISON_OPERATORS:
operation = COMPARISON_OPERATORS[operator]
if is_compiled(left) and is_compiled(right):
# Possible, because the return is not an option. Just compare.
left = left.obj
right = right.obj
try:
result = operation(left, right)
except TypeError:
# Could be True or False.
return ContextSet(compiled.create(evaluator, True), compiled.create(evaluator, False))
else:
return ContextSet(compiled.create(evaluator, result))
elif operator == 'in':
return NO_CONTEXTS
def check(obj):
"""Checks if a Jedi object is either a float or an int."""
return isinstance(obj, instance.CompiledInstance) and \
obj.name.string_name in ('int', 'float')
# Static analysis, one is a number, the other one is not.
if operator in ('+', '-') and l_is_num != r_is_num \
and not (check(left) or check(right)):
message = "TypeError: unsupported operand type(s) for +: %s and %s"
analysis.add(context, 'type-error-operation', operator,
message % (left, right))
return ContextSet(left, right)

View File

@@ -121,7 +121,7 @@ def _paths_from_assignment(module_context, expr_stmt):
continue continue
from jedi.evaluate.iterable import py__iter__ from jedi.evaluate.iterable import py__iter__
from jedi.evaluate.precedence import is_string from jedi.evaluate.syntax_tree import is_string
cn = ContextualizedNode(module_context.create_context(expr_stmt), expr_stmt) cn = ContextualizedNode(module_context.create_context(expr_stmt), expr_stmt)
for lazy_context in py__iter__(module_context.evaluator, cn.infer(), cn): for lazy_context in py__iter__(module_context.evaluator, cn.infer(), cn):
for context in lazy_context.infer(): for context in lazy_context.infer():