mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 14:34:31 +08:00
Fixes to the isinstance tests.
This commit is contained in:
@@ -111,8 +111,8 @@ class Evaluator(object):
|
|||||||
self.recursion_detector = recursion.RecursionDetector(self)
|
self.recursion_detector = recursion.RecursionDetector(self)
|
||||||
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
|
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
|
||||||
|
|
||||||
def find_types(self, context, name_str, position=None, search_global=False,
|
def find_types(self, context, name_or_str, name_context, position=None,
|
||||||
is_goto=False):
|
search_global=False, is_goto=False):
|
||||||
"""
|
"""
|
||||||
This is the search function. The most important part to debug.
|
This is the search function. The most important part to debug.
|
||||||
`remove_statements` and `filter_statements` really are the core part of
|
`remove_statements` and `filter_statements` really are the core part of
|
||||||
@@ -121,7 +121,7 @@ class Evaluator(object):
|
|||||||
:param position: Position of the last statement -> tuple of line, column
|
:param position: Position of the last statement -> tuple of line, column
|
||||||
:return: List of Names. Their parents are the types.
|
:return: List of Names. Their parents are the types.
|
||||||
"""
|
"""
|
||||||
f = finder.NameFinder(self, context, name_str, position)
|
f = finder.NameFinder(self, context, name_context, name_or_str, position)
|
||||||
filters = f.get_filters(search_global)
|
filters = f.get_filters(search_global)
|
||||||
if is_goto:
|
if is_goto:
|
||||||
return f.filter_name(filters)
|
return f.filter_name(filters)
|
||||||
@@ -152,7 +152,8 @@ class Evaluator(object):
|
|||||||
operator = copy.copy(first_operation)
|
operator = copy.copy(first_operation)
|
||||||
operator.value = operator.value[:-1]
|
operator.value = operator.value[:-1]
|
||||||
name = str(stmt.get_defined_names()[0])
|
name = str(stmt.get_defined_names()[0])
|
||||||
left = self.find_types(context, name, stmt.start_pos, search_global=True)
|
left = context.py__getattribute__(
|
||||||
|
name, position=stmt.start_pos, search_global=True)
|
||||||
|
|
||||||
for_stmt = stmt.get_parent_until(tree.ForStmt)
|
for_stmt = stmt.get_parent_until(tree.ForStmt)
|
||||||
if isinstance(for_stmt, tree.ForStmt) and types \
|
if isinstance(for_stmt, tree.ForStmt) and types \
|
||||||
@@ -294,8 +295,10 @@ class Evaluator(object):
|
|||||||
types = self._eval_atom(context, element.children[0])
|
types = self._eval_atom(context, element.children[0])
|
||||||
for next_name in element.children[2::2]:
|
for next_name in element.children[2::2]:
|
||||||
# TODO add search_global=True?
|
# TODO add search_global=True?
|
||||||
types = set(chain.from_iterable(self.find_types(typ, next_name)
|
types = unite(
|
||||||
for typ in types))
|
typ.py__getattribute__(next_name, name_context=context)
|
||||||
|
for typ in types
|
||||||
|
)
|
||||||
types = types
|
types = types
|
||||||
elif element.type == 'eval_input':
|
elif element.type == 'eval_input':
|
||||||
types = self._eval_element_not_cached(context, element.children[0])
|
types = self._eval_element_not_cached(context, element.children[0])
|
||||||
@@ -326,7 +329,11 @@ class Evaluator(object):
|
|||||||
# We only need to adjust the start_pos for statements, because
|
# We only need to adjust the start_pos for statements, because
|
||||||
# there the name cannot be used.
|
# there the name cannot be used.
|
||||||
stmt = atom
|
stmt = atom
|
||||||
return self.find_types(context, atom, stmt.start_pos, search_global=True)
|
return context.py__getattribute__(
|
||||||
|
name_or_str=atom,
|
||||||
|
position=stmt.start_pos,
|
||||||
|
search_global=True
|
||||||
|
)
|
||||||
elif isinstance(atom, tree.Literal):
|
elif isinstance(atom, tree.Literal):
|
||||||
return set([compiled.create(self, atom.eval())])
|
return set([compiled.create(self, atom.eval())])
|
||||||
else:
|
else:
|
||||||
@@ -372,7 +379,10 @@ class Evaluator(object):
|
|||||||
for typ in types:
|
for typ in types:
|
||||||
debug.dbg('eval_trailer: %s in scope %s', trailer, typ)
|
debug.dbg('eval_trailer: %s in scope %s', trailer, typ)
|
||||||
if trailer_op == '.':
|
if trailer_op == '.':
|
||||||
new_types |= self.find_types(typ, node)
|
new_types |= typ.py__getattribute__(
|
||||||
|
name_context=context,
|
||||||
|
name_or_str=node
|
||||||
|
)
|
||||||
elif trailer_op == '(':
|
elif trailer_op == '(':
|
||||||
arguments = param.TreeArguments(self, context, node, trailer)
|
arguments = param.TreeArguments(self, context, node, trailer)
|
||||||
new_types |= self.execute(typ, arguments)
|
new_types |= self.execute(typ, arguments)
|
||||||
@@ -477,27 +487,32 @@ class Evaluator(object):
|
|||||||
new_dotted.children[index - 1:] = []
|
new_dotted.children[index - 1:] = []
|
||||||
values = self.eval_element(context, new_dotted)
|
values = self.eval_element(context, new_dotted)
|
||||||
return unite(
|
return unite(
|
||||||
self.find_types(value, name, is_goto=True) for value in values
|
value.py__getattribute__(name, name_context=context, is_goto=True)
|
||||||
|
for value in values
|
||||||
)
|
)
|
||||||
return resolve_implicit_imports(iterable.unite(
|
#return resolve_implicit_imports(iterable.unite(
|
||||||
self.find_types(typ, name, is_goto=True) for typ in types
|
#self.find_types(typ, name, is_goto=True) for typ in types
|
||||||
))
|
#))
|
||||||
|
|
||||||
if tree.is_node(par, 'trailer') and par.children[0] == '.':
|
if tree.is_node(par, 'trailer') and par.children[0] == '.':
|
||||||
values = helpers.evaluate_call_of_leaf(context, name, cut_own_trailer=True)
|
values = helpers.evaluate_call_of_leaf(context, name, cut_own_trailer=True)
|
||||||
return unite(
|
return unite(
|
||||||
self.find_types(value, name, is_goto=True) for value in values
|
value.py__getattribute__(name, name_context=context, is_goto=True)
|
||||||
|
for value in values
|
||||||
)
|
)
|
||||||
return resolve_implicit_imports(iterable.unite(
|
#return resolve_implicit_imports(iterable.unite(
|
||||||
self.find_types(typ, name, is_goto=True) for typ in types
|
#self.find_types(typ, name, is_goto=True) for typ in types
|
||||||
))
|
#))
|
||||||
else:
|
else:
|
||||||
if stmt.type != 'expr_stmt':
|
if stmt.type != 'expr_stmt':
|
||||||
# We only need to adjust the start_pos for statements, because
|
# We only need to adjust the start_pos for statements, because
|
||||||
# there the name cannot be used.
|
# there the name cannot be used.
|
||||||
stmt = name
|
stmt = name
|
||||||
return self.find_types(context, name, stmt.start_pos,
|
return context.py__getattribute__(
|
||||||
search_global=True, is_goto=True)
|
name,
|
||||||
|
position=stmt.start_pos,
|
||||||
|
search_global=True, is_goto=True
|
||||||
|
)
|
||||||
|
|
||||||
def wrap(self, element, parent_context):
|
def wrap(self, element, parent_context):
|
||||||
raise DeprecationWarning
|
raise DeprecationWarning
|
||||||
|
|||||||
@@ -46,9 +46,12 @@ class Context(object):
|
|||||||
def eval_trailer(self, types, trailer):
|
def eval_trailer(self, types, trailer):
|
||||||
return self.evaluator.eval_trailer(self, types, trailer)
|
return self.evaluator.eval_trailer(self, types, trailer)
|
||||||
|
|
||||||
def py__getattribute__(self, name_or_str, position=None,
|
def py__getattribute__(self, name_or_str, name_context=None, position=None,
|
||||||
search_global=False, is_goto=False):
|
search_global=False, is_goto=False):
|
||||||
return self.evaluator.find_types(self, name_or_str, position, search_global, is_goto)
|
if name_context is None:
|
||||||
|
name_context = self
|
||||||
|
return self.evaluator.find_types(
|
||||||
|
self, name_or_str, name_context, position, search_global, is_goto)
|
||||||
|
|
||||||
def create_context(self, node):
|
def create_context(self, node):
|
||||||
return self.evaluator.create_context(self, node)
|
return self.evaluator.create_context(self, node)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ from jedi.evaluate import analysis
|
|||||||
from jedi.evaluate import flow_analysis
|
from jedi.evaluate import flow_analysis
|
||||||
from jedi.evaluate import param
|
from jedi.evaluate import param
|
||||||
from jedi.evaluate import helpers
|
from jedi.evaluate import helpers
|
||||||
|
from jedi.evaluate.context import TreeContext
|
||||||
from jedi.evaluate.cache import memoize_default
|
from jedi.evaluate.cache import memoize_default
|
||||||
from jedi.evaluate.filters import get_global_filters, ContextName
|
from jedi.evaluate.filters import get_global_filters, ContextName
|
||||||
|
|
||||||
@@ -95,10 +96,11 @@ def filter_definition_names(names, origin, position=None):
|
|||||||
|
|
||||||
|
|
||||||
class NameFinder(object):
|
class NameFinder(object):
|
||||||
def __init__(self, evaluator, context, name_or_str, position=None):
|
def __init__(self, evaluator, context, name_context, name_or_str, position=None):
|
||||||
self._evaluator = evaluator
|
self._evaluator = evaluator
|
||||||
# Make sure that it's not just a syntax tree node.
|
# Make sure that it's not just a syntax tree node.
|
||||||
self._context = context
|
self._context = context
|
||||||
|
self._name_context = name_context
|
||||||
self._name = name_or_str
|
self._name = name_or_str
|
||||||
if isinstance(name_or_str, tree.Name):
|
if isinstance(name_or_str, tree.Name):
|
||||||
self._string_name = name_or_str.value
|
self._string_name = name_or_str.value
|
||||||
@@ -308,19 +310,17 @@ class NameFinder(object):
|
|||||||
types = set()
|
types = set()
|
||||||
|
|
||||||
# Add isinstance and other if/assert knowledge.
|
# Add isinstance and other if/assert knowledge.
|
||||||
#if isinstance(self._name, tree.Name):
|
if isinstance(self._name, tree.Name) and \
|
||||||
## Ignore FunctionExecution parents for now.
|
not isinstance(self._name_context, AbstractInstanceContext):
|
||||||
#flow_scope = self._name
|
# Ignore FunctionExecution parents for now.
|
||||||
#until = flow_scope.get_parent_until(er.FunctionExecution)
|
flow_scope = self._name
|
||||||
#while not isinstance(until, er.FunctionExecution):
|
while flow_scope != self._name_context.get_node():
|
||||||
#flow_scope = flow_scope.get_parent_scope(include_flows=True)
|
flow_scope = flow_scope.get_parent_scope(include_flows=True)
|
||||||
#if flow_scope is None:
|
# TODO check if result is in scope -> no evaluation necessary
|
||||||
#break
|
n = check_flow_information(self._name_context, flow_scope,
|
||||||
## TODO check if result is in scope -> no evaluation necessary
|
self._name, self._position)
|
||||||
#n = check_flow_information(self._evaluator, flow_scope,
|
if n:
|
||||||
#self._name, self._position)
|
return n
|
||||||
#if n:
|
|
||||||
#return n
|
|
||||||
|
|
||||||
for name in names:
|
for name in names:
|
||||||
new_types = name.infer()
|
new_types = name.infer()
|
||||||
@@ -515,14 +515,14 @@ def _eval_param(evaluator, context, param, scope):
|
|||||||
if not res_new:
|
if not res_new:
|
||||||
if param.stars:
|
if param.stars:
|
||||||
t = 'tuple' if param.stars == 1 else 'dict'
|
t = 'tuple' if param.stars == 1 else 'dict'
|
||||||
typ = list(evaluator.find_types(evaluator.BUILTINS, t))[0]
|
typ = list(evaluator.BUILTINS.py__getattribute__(t))[0]
|
||||||
res_new = evaluator.execute(typ)
|
res_new = evaluator.execute(typ)
|
||||||
if param.default:
|
if param.default:
|
||||||
res_new |= evaluator.eval_element(context, param.default)
|
res_new |= evaluator.eval_element(context, param.default)
|
||||||
return res_new
|
return res_new
|
||||||
|
|
||||||
|
|
||||||
def check_flow_information(evaluator, flow, search_name, pos):
|
def check_flow_information(context, flow, search_name, pos):
|
||||||
""" Try to find out the type of a variable just with the information that
|
""" Try to find out the type of a variable just with the information that
|
||||||
is given by the flows: e.g. It is also responsible for assert checks.::
|
is given by the flows: e.g. It is also responsible for assert checks.::
|
||||||
|
|
||||||
@@ -545,7 +545,7 @@ def check_flow_information(evaluator, flow, search_name, pos):
|
|||||||
for name in names:
|
for name in names:
|
||||||
ass = name.get_parent_until(tree.AssertStmt)
|
ass = name.get_parent_until(tree.AssertStmt)
|
||||||
if isinstance(ass, tree.AssertStmt) and pos is not None and ass.start_pos < pos:
|
if isinstance(ass, tree.AssertStmt) and pos is not None and ass.start_pos < pos:
|
||||||
result = _check_isinstance_type(evaluator, ass.assertion(), search_name)
|
result = _check_isinstance_type(context, ass.assertion(), search_name)
|
||||||
if result:
|
if result:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -553,11 +553,11 @@ def check_flow_information(evaluator, flow, search_name, pos):
|
|||||||
potential_ifs = [c for c in flow.children[1::4] if c != ':']
|
potential_ifs = [c for c in flow.children[1::4] if c != ':']
|
||||||
for if_test in reversed(potential_ifs):
|
for if_test in reversed(potential_ifs):
|
||||||
if search_name.start_pos > if_test.end_pos:
|
if search_name.start_pos > if_test.end_pos:
|
||||||
return _check_isinstance_type(evaluator, if_test, search_name)
|
return _check_isinstance_type(context, if_test, search_name)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _check_isinstance_type(evaluator, element, search_name):
|
def _check_isinstance_type(context, element, search_name):
|
||||||
try:
|
try:
|
||||||
assert element.type in ('power', 'atom_expr')
|
assert element.type in ('power', 'atom_expr')
|
||||||
# this might be removed if we analyze and, etc
|
# this might be removed if we analyze and, etc
|
||||||
@@ -569,27 +569,29 @@ def _check_isinstance_type(evaluator, element, search_name):
|
|||||||
|
|
||||||
# arglist stuff
|
# arglist stuff
|
||||||
arglist = trailer.children[1]
|
arglist = trailer.children[1]
|
||||||
args = param.Arguments(evaluator, arglist, trailer)
|
args = param.TreeArguments(context.evaluator, context, arglist, trailer)
|
||||||
lst = list(args.unpack())
|
param_list = list(args.unpack())
|
||||||
# Disallow keyword arguments
|
# Disallow keyword arguments
|
||||||
assert len(lst) == 2 and lst[0][0] is None and lst[1][0] is None
|
assert len(param_list) == 2
|
||||||
name = lst[0][1][0] # first argument, values, first value
|
(key1, lazy_context_object), (key2, lazy_context_cls) = param_list
|
||||||
|
assert key1 is None and key2 is None
|
||||||
|
call = helpers.call_of_leaf(search_name)
|
||||||
|
is_instance_call = helpers.call_of_leaf(lazy_context_object.data)
|
||||||
# Do a simple get_code comparison. They should just have the same code,
|
# Do a simple get_code comparison. They should just have the same code,
|
||||||
# and everything will be all right.
|
# and everything will be all right.
|
||||||
classes = lst[1][1][0]
|
assert is_instance_call.get_code(normalized=True) == call.get_code(normalized=True)
|
||||||
call = helpers.call_of_leaf(search_name)
|
|
||||||
assert name.get_code(normalized=True) == call.get_code(normalized=True)
|
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
result = set()
|
result = set()
|
||||||
for cls_or_tup in evaluator.eval_element(classes):
|
for cls_or_tup in lazy_context_cls.infer():
|
||||||
if isinstance(cls_or_tup, iterable.Array) and cls_or_tup.type == 'tuple':
|
if isinstance(cls_or_tup, iterable.AbstractSequence) and \
|
||||||
|
cls_or_tup.array_type == 'tuple':
|
||||||
for lazy_context in cls_or_tup.py__iter__():
|
for lazy_context in cls_or_tup.py__iter__():
|
||||||
for context in lazy_context.infer():
|
for context in lazy_context.infer():
|
||||||
result |= context.execute_evaluated()
|
result |= context.execute_evaluated()
|
||||||
else:
|
else:
|
||||||
result |= evaluator.execute(cls_or_tup)
|
result |= cls_or_tup.execute_evaluated()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,49 @@ def evaluate_call_of_leaf(context, leaf, cut_own_trailer=False):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
def get_names_of_node(node):
|
||||||
try:
|
try:
|
||||||
children = node.children
|
children = node.children
|
||||||
|
|||||||
@@ -92,9 +92,11 @@ class ImportWrapper(object):
|
|||||||
|
|
||||||
if from_import_name is not None:
|
if from_import_name is not None:
|
||||||
types = unite(
|
types = unite(
|
||||||
evaluator.find_types(t, unicode(from_import_name),
|
t.py__getattribute__(
|
||||||
is_goto=is_goto)
|
unicode(from_import_name),
|
||||||
for t in types
|
name_context=self._context,
|
||||||
|
is_goto=is_goto
|
||||||
|
) for t in types
|
||||||
)
|
)
|
||||||
|
|
||||||
if not types:
|
if not types:
|
||||||
@@ -316,7 +318,7 @@ class Importer(object):
|
|||||||
# that is being achieved by messing with ``sys.modules`` in
|
# that is being achieved by messing with ``sys.modules`` in
|
||||||
# ``os``.
|
# ``os``.
|
||||||
if [str(i) for i in import_path] == ['os', 'path']:
|
if [str(i) for i in import_path] == ['os', 'path']:
|
||||||
return self._evaluator.find_types(parent_module, 'path')
|
return parent_module.py__getattribute__('path')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
method = parent_module.py__path__
|
method = parent_module.py__path__
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def _fix_forward_reference(context, node):
|
|||||||
else:
|
else:
|
||||||
module = node.get_parent_until()
|
module = node.get_parent_until()
|
||||||
new_node.move(module.end_pos[0])
|
new_node.move(module.end_pos[0])
|
||||||
new_node.parent = module
|
new_node.parent = context.get_node()
|
||||||
return new_node
|
return new_node
|
||||||
else:
|
else:
|
||||||
return node
|
return node
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ def builtins_getattr(evaluator, objects, names, defaults=None):
|
|||||||
for obj in objects:
|
for obj in objects:
|
||||||
for name in names:
|
for name in names:
|
||||||
if precedence.is_string(name):
|
if precedence.is_string(name):
|
||||||
return evaluator.find_types(obj, name.obj)
|
return obj.py__getattribute__(name.obj)
|
||||||
else:
|
else:
|
||||||
debug.warning('getattr called without str')
|
debug.warning('getattr called without str')
|
||||||
continue
|
continue
|
||||||
@@ -185,12 +185,12 @@ def builtins_isinstance(evaluator, objects, types, arguments):
|
|||||||
classes = unite(cls_or_tup.py__iter__())
|
classes = unite(cls_or_tup.py__iter__())
|
||||||
bool_results.add(any(cls in mro for cls in classes))
|
bool_results.add(any(cls in mro for cls in classes))
|
||||||
else:
|
else:
|
||||||
_, nodes = list(arguments.unpack())[1]
|
_, lazy_context = list(arguments.unpack())[1]
|
||||||
for node in nodes:
|
node = lazy_context.data
|
||||||
message = 'TypeError: isinstance() arg 2 must be a ' \
|
message = 'TypeError: isinstance() arg 2 must be a ' \
|
||||||
'class, type, or tuple of classes and types, ' \
|
'class, type, or tuple of classes and types, ' \
|
||||||
'not %s.' % cls_or_tup
|
'not %s.' % cls_or_tup
|
||||||
analysis.add(evaluator, 'type-error-isinstance', node, message)
|
analysis.add(evaluator, 'type-error-isinstance', node, message)
|
||||||
|
|
||||||
return set(compiled.create(evaluator, x) for x in bool_results)
|
return set(compiled.create(evaluator, x) for x in bool_results)
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ from jedi._compatibility import unicode, is_py3
|
|||||||
from jedi.parser import Parser, load_grammar
|
from jedi.parser import Parser, load_grammar
|
||||||
from jedi.api.classes import Definition
|
from jedi.api.classes import Definition
|
||||||
from jedi.api.completion import get_user_scope
|
from jedi.api.completion import get_user_scope
|
||||||
from jedi.evaluate.representation import ModuleContext
|
|
||||||
|
|
||||||
|
|
||||||
TEST_COMPLETIONS = 0
|
TEST_COMPLETIONS = 0
|
||||||
@@ -193,8 +192,8 @@ class IntegrationTestCase(object):
|
|||||||
element = parser.get_parsed_node()
|
element = parser.get_parsed_node()
|
||||||
module_context = script._get_module()
|
module_context = script._get_module()
|
||||||
# The context shouldn't matter for the test results.
|
# The context shouldn't matter for the test results.
|
||||||
element.parent = module_context.module_node
|
|
||||||
user_context = get_user_scope(module_context, (self.line_nr, 0))
|
user_context = get_user_scope(module_context, (self.line_nr, 0))
|
||||||
|
element.parent = user_context.get_node()
|
||||||
results = evaluator.eval_element(user_context, element)
|
results = evaluator.eval_element(user_context, element)
|
||||||
if not results:
|
if not results:
|
||||||
raise Exception('Could not resolve %s on line %s'
|
raise Exception('Could not resolve %s on line %s'
|
||||||
|
|||||||
Reference in New Issue
Block a user