diff --git a/jedi/evaluate/iterable.py b/jedi/evaluate/iterable.py index 524d843f..9e7fec4c 100644 --- a/jedi/evaluate/iterable.py +++ b/jedi/evaluate/iterable.py @@ -29,7 +29,7 @@ from jedi.evaluate import compiled from jedi.evaluate import helpers from jedi.evaluate import analysis 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.cache import evaluator_method_cache from jedi.evaluate.filters import DictFilter, AbstractNameDefinition, \ @@ -462,7 +462,7 @@ class SequenceLiteralContext(ArrayMixin, AbstractSequence): """ for key_node, value in self._items(): 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) def __repr__(self): diff --git a/jedi/evaluate/precedence.py b/jedi/evaluate/precedence.py deleted file mode 100644 index 54973777..00000000 --- a/jedi/evaluate/precedence.py +++ /dev/null @@ -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) diff --git a/jedi/evaluate/stdlib.py b/jedi/evaluate/stdlib.py index 447079c1..ab758c79 100644 --- a/jedi/evaluate/stdlib.py +++ b/jedi/evaluate/stdlib.py @@ -20,11 +20,11 @@ from jedi.evaluate.instance import InstanceFunctionExecution, \ AnonymousInstanceFunctionExecution from jedi.evaluate import iterable from jedi import debug -from jedi.evaluate import precedence from jedi.evaluate import param from jedi.evaluate import analysis from jedi.evaluate.context import LazyTreeContext, ContextualizedNode, \ 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 # __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 for obj in objects: for name in names: - if precedence.is_string(name): + if is_string(name): return obj.py__getattribute__(name.obj) else: debug.warning('getattr called without str') diff --git a/jedi/evaluate/syntax_tree.py b/jedi/evaluate/syntax_tree.py index 84b6c0bf..0c4ad194 100644 --- a/jedi/evaluate/syntax_tree.py +++ b/jedi/evaluate/syntax_tree.py @@ -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) diff --git a/jedi/evaluate/sys_path.py b/jedi/evaluate/sys_path.py index 057479ec..e78added 100644 --- a/jedi/evaluate/sys_path.py +++ b/jedi/evaluate/sys_path.py @@ -121,7 +121,7 @@ def _paths_from_assignment(module_context, expr_stmt): continue 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) for lazy_context in py__iter__(module_context.evaluator, cn.infer(), cn): for context in lazy_context.infer():