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

@@ -2,18 +2,19 @@
Functions evaluating the syntax tree.
"""
import copy
import operator as op
from parso.python import tree
from jedi import debug
from jedi import parser_utils
from jedi.evaluate.context import ContextSet, NO_CONTEXTS, ContextualizedNode, \
ContextualizedName
ContextualizedName, iterator_to_context_set
from jedi.evaluate import compiled
from jedi.evaluate import precedence
from jedi.evaluate import pep0484
from jedi.evaluate import recursion
from jedi.evaluate import helpers
from jedi.evaluate import analysis
def _limit_context_infers(func):
@@ -66,7 +67,7 @@ def eval_node(context, element):
for trailer in element.children[1:]:
if trailer == '**': # has a power operation.
right = evaluator.eval_element(context, element.children[2])
context_set = precedence.calculate(
context_set = calculate(
evaluator,
context,
context_set,
@@ -84,7 +85,7 @@ def eval_node(context, element):
elif typ in ('not_test', 'factor'):
context_set = context.eval_node(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
elif typ == 'test':
# `x if foo else y` case.
@@ -162,7 +163,7 @@ def eval_atom(context, atom):
context_set = eval_atom(context, c[0])
for string in c[1:]:
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
# Parentheses without commas are not tuples.
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()}
with helpers.predefine_names(context, for_stmt, dct):
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
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)
return context_set
@@ -276,7 +277,152 @@ def eval_or_test(context, or_test):
types = context.eval_node(right)
# Otherwise continue, because of uncertainty.
else:
types = precedence.calculate(context.evaluator, context, types, operator,
types = calculate(context.evaluator, context, types, operator,
context.eval_node(right))
debug.dbg('calculate_children types %s', 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)