diff --git a/README.rst b/README.rst index 3e746b56..254bc8e9 100644 --- a/README.rst +++ b/README.rst @@ -60,14 +60,15 @@ Jedi supports many of the widely used Python features: - class decorators (py3k feature, are being ignored too, until I find a use case, that doesn't work with Jedi) - simple/usual ``sys.path`` modifications +- ``isinstance`` checks for if/while However, it does not yet support (and probably will in future versions, because they are on my todo list): -- assert / isinstance +- operation support -> [3]\*3 etc. - manipulations of instances outside the instance variables, without using functions -- operation support -> ``__mul__``, ``__add__``, etc. +- assert It does not support (and most probably will not in future versions): diff --git a/jedi/dynamic.py b/jedi/dynamic.py index 53a617f4..47316156 100644 --- a/jedi/dynamic.py +++ b/jedi/dynamic.py @@ -492,3 +492,43 @@ class RelatedName(BaseOutput): def __hash__(self): return hash((self.start_pos, self.module_path)) + + +def check_flow_information(flow, search_name): + """ Try to find out the type of a variable just with the information that + is given by the flows: e.g. + >>> if isinstance(k, str): + >>> k. # <- completion here + + ensures that `k` is a string. + """ + try: + assert flow.command in ['if', 'while'] + assert len(flow.inits) == 1 + ass = flow.inits[0].get_assignment_calls() + assert len(ass.values) == 1 and len(ass.values[0]) == 1 + call = ass.values[0][0] + assert type(call) == parsing.Call and str(call.name) == 'isinstance' + assert bool(call.execution) + + # isinstance check + isinst = call.execution.values + assert len(isinst) == 2 + assert len(isinst[0]) == 1 + assert len(isinst[1]) == 1 + assert isinstance(isinst[0][0], parsing.Call) + # names fit? + assert str(isinst[0][0].name) == search_name + classes_call = isinst[1][0] # class_or_type_or_tuple + assert isinstance(classes_call, parsing.Call) + result = [] + for c in evaluate.follow_call(classes_call): + if isinstance(c, evaluate.Array): + result += c.get_index_types() + else: + result.append(c) + for i, c in enumerate(result): + result[i] = evaluate.Instance(c) + return result + except AssertionError: + return [] diff --git a/jedi/evaluate.py b/jedi/evaluate.py index e5d4a9e0..b14a09f6 100644 --- a/jedi/evaluate.py +++ b/jedi/evaluate.py @@ -1118,10 +1118,11 @@ def get_scopes_for_name(scope, name_str, position=None, search_global=False, result.append(par) return result, no_break_scope + flow_scope = scope result = [] # compare func uses the tuple of line/indent = line/column comparison_func = lambda name: (name.start_pos) - for scope, name_list in scope_generator: + for nscope, name_list in scope_generator: break_scopes = [] # here is the position stuff happening (sorting of variables) for name in sorted(name_list, key=comparison_func, reverse=True): @@ -1136,7 +1137,7 @@ def get_scopes_for_name(scope, name_str, position=None, search_global=False, else: result += r # for comparison we need the raw class - s = scope.base if isinstance(scope, Class) else scope + s = nscope.base if isinstance(nscope, Class) else nscope # this means that a definition was found and is not e.g. # in if/else. if result and not no_break_scope: @@ -1147,10 +1148,19 @@ def get_scopes_for_name(scope, name_str, position=None, search_global=False, if result: break - if not result and isinstance(scope, Instance): + while flow_scope and flow_scope.isinstance(parsing.Flow): + result = dynamic.check_flow_information(flow_scope, name_str) + if result: + break + flow_scope = flow_scope.parent() + flow_scope = nscope + if result: + break + + if not result and isinstance(nscope, Instance): # getattr() / __getattr__ / __getattribute__ - result += check_getattr(scope, name_str) - debug.dbg('sfn filter "%s" in %s: %s' % (name_str, scope, result)) + result += check_getattr(nscope, name_str) + debug.dbg('sfn filter "%s" in %s: %s' % (name_str, nscope, result)) return result def descriptor_check(result): diff --git a/test/completion/isinstance.py b/test/completion/isinstance.py new file mode 100644 index 00000000..db0a4e4f --- /dev/null +++ b/test/completion/isinstance.py @@ -0,0 +1,19 @@ +if isinstance(i, str): + #? str() + i + +if isinstance(j, (str, int)): + #? str() int() + j + +while isinstance(k, (str, int)): + #? str() int() + k + +if not isinstance(k, (str, int)): + #? + k + +while not isinstance(k, (str, int)): + #? + k