diff --git a/README.rst b/README.rst index 86e9ae3e..2f276ecf 100644 --- a/README.rst +++ b/README.rst @@ -60,14 +60,13 @@ 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 +- ``isinstance`` checks for if/while/assert However, it does not yet support (and probably will in future versions, because they are on my todo list): - manipulations of instances outside the instance variables, without using functions -- assert It does not support (and most probably will not in future versions): diff --git a/jedi/dynamic.py b/jedi/dynamic.py index 47316156..b113a5b7 100644 --- a/jedi/dynamic.py +++ b/jedi/dynamic.py @@ -494,18 +494,32 @@ class RelatedName(BaseOutput): return hash((self.start_pos, self.module_path)) -def check_flow_information(flow, search_name): +def check_flow_information(flow, search_name, pos): """ Try to find out the type of a variable just with the information that - is given by the flows: e.g. + is given by the flows: e.g. It is also responsible for assert checks. >>> if isinstance(k, str): >>> k. # <- completion here ensures that `k` is a string. """ + result = [] + if isinstance(flow, parsing.Scope) and not result: + for ass in reversed(flow.asserts): + if ass.start_pos > pos: + continue + result = check_statement_information(ass, search_name) + if result: + break + + if isinstance(flow, parsing.Flow) and not result: + if flow.command in ['if', 'while'] and len(flow.inits) == 1: + result = check_statement_information(flow.inits[0], search_name) + return result + + +def check_statement_information(stmt, search_name): try: - assert flow.command in ['if', 'while'] - assert len(flow.inits) == 1 - ass = flow.inits[0].get_assignment_calls() + ass = stmt.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' diff --git a/jedi/evaluate.py b/jedi/evaluate.py index c0502ba4..7a17b946 100644 --- a/jedi/evaluate.py +++ b/jedi/evaluate.py @@ -264,7 +264,7 @@ class Instance(use_metaclass(CachedMetaClass, Executable)): def __getattr__(self, name): if name not in ['start_pos', 'end_pos', 'name', 'get_imports', - 'docstr']: + 'docstr', 'asserts']: raise AttributeError("Instance %s: Don't touch this (%s)!" % (self, name)) return getattr(self.base, name) @@ -386,7 +386,7 @@ class Class(use_metaclass(CachedMetaClass, parsing.Base)): def __getattr__(self, name): if name not in ['start_pos', 'end_pos', 'parent', 'subscopes', - 'get_imports', 'get_parent_until', 'docstr']: + 'get_imports', 'get_parent_until', 'docstr', 'asserts']: raise AttributeError("Don't touch this (%s)!" % name) return getattr(self.base, name) @@ -726,6 +726,11 @@ class Execution(Executable): def returns(self): return self.copy_properties('returns') + @property + @memoize_default() + def asserts(self): + return self.copy_properties('asserts') + @property @memoize_default() def statements(self): @@ -1148,10 +1153,13 @@ def get_scopes_for_name(scope, name_str, position=None, search_global=False, if result: break - while flow_scope and flow_scope.isinstance(parsing.Flow): - result = dynamic.check_flow_information(flow_scope, name_str) + while flow_scope: + result = dynamic.check_flow_information(flow_scope, name_str, + position) if result: break + if flow_scope == nscope: + break flow_scope = flow_scope.parent() flow_scope = nscope if result: diff --git a/jedi/helpers.py b/jedi/helpers.py index 111d7f38..788f4f65 100644 --- a/jedi/helpers.py +++ b/jedi/helpers.py @@ -92,7 +92,7 @@ class ExecutionRecursionDecorator(object): self.reset() def __call__(self, execution, evaluate_generator=False): - debug.dbg('Execution recursions: ', execution, self.recursion_level, + debug.dbg('Execution recursions: %s' % execution, self.recursion_level, self.execution_count, len(self.execution_funcs)) if self.check_recursion(execution, evaluate_generator): result = [] diff --git a/jedi/parsing.py b/jedi/parsing.py index 312336a0..1a4c2940 100644 --- a/jedi/parsing.py +++ b/jedi/parsing.py @@ -139,6 +139,7 @@ class Scope(Simple): self.imports = [] self.statements = [] self.docstr = docstr + self.asserts = [] def add_scope(self, sub, decorators): sub.parent = weakref.ref(self) @@ -1416,7 +1417,7 @@ class PyFuzzyParser(object): string += ".".join(n.names) continue elif token_type == tokenize.NAME: - if tok in ['return', 'yield', 'del', 'raise', 'assert']: + if tok in ['return', 'yield', 'del', 'raise']: if len(tok_list) > 1: # this happens, when a statement has opening # brackets, which are not closed again, here I just @@ -1751,6 +1752,10 @@ class PyFuzzyParser(object): decorators.append(stmt) elif tok == 'pass': continue + elif tok == 'assert': + stmt, tok = self._parse_statement() + stmt.parent = weakref.ref(self.scope) + self.scope.asserts.append(stmt) # default elif token_type in [tokenize.NAME, tokenize.STRING, tokenize.NUMBER] \ diff --git a/test/completion/isinstance.py b/test/completion/isinstance.py index db0a4e4f..01d1f7a5 100644 --- a/test/completion/isinstance.py +++ b/test/completion/isinstance.py @@ -17,3 +17,14 @@ if not isinstance(k, (str, int)): while not isinstance(k, (str, int)): #? k + +assert isinstance(ass, int): +#? int() +ass + +assert isinstance(ass, str): +assert not isinstance(ass, int): + +if 2: + #? str() + ass