def matches_signature(self, arguments): from medi.inference.param import get_executed_param_names_and_issues executed_param_names, issues = \ get_executed_param_names_and_issues(self._function_value, arguments) if issues: return False matches = all(executed_param_name.matches_signature() for executed_param_name in executed_param_names) if debug.enable_notice: tree_node = self._function_value.tree_node signature = parser_utils.get_signature(tree_node) if matches: debug.dbg("Overloading match: %s@%s (%s)", signature, tree_node.start_pos[0], arguments, color='BLUE') else: debug.dbg("Overloading no match: %s@%s (%s)", signature, tree_node.start_pos[0], arguments, color='BLUE') return matches
def _get_annotated_class_object(self): from medi.inference.gradual.annotation import py__annotations__, \ infer_type_vars_for_execution args = InstanceArguments(self, self._arguments) for signature in self.class_value.py__getattribute__( '__init__').get_signatures(): # Just take the first result, it should always be one, because we # control the typeshed code. funcdef = signature.value.tree_node if funcdef is None or funcdef.type != 'funcdef' \ or not signature.matches_signature(args): # First check if the signature even matches, if not we don't # need to infer anything. continue bound_method = BoundMethod(self, self.class_value.as_context(), signature.value) all_annotations = py__annotations__(funcdef) type_var_dict = infer_type_vars_for_execution( bound_method, args, all_annotations) if type_var_dict: defined, = self.class_value.define_generics( infer_type_vars_for_execution(signature.value, args, all_annotations), ) debug.dbg('Inferred instance value as %s', defined, color='BLUE') return defined return None
def _parse_function_doc(doc): """ Takes a function and returns the params and return value as a tuple. This is nothing more than a docstring parser. TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None TODO docstrings like 'tuple of integers' """ doc = force_unicode(doc) # parse round parentheses: def func(a, (b,c)) try: count = 0 start = doc.index('(') for i, s in enumerate(doc[start:]): if s == '(': count += 1 elif s == ')': count -= 1 if count == 0: end = start + i break param_str = doc[start + 1:end] except (ValueError, UnboundLocalError): # ValueError for doc.index # UnboundLocalError for undefined end in last line debug.dbg('no brackets found - no param') end = 0 param_str = u'' else: # remove square brackets, that show an optional param ( = None) def change_options(m): args = m.group(1).split(',') for i, a in enumerate(args): if a and '=' not in a: args[i] += '=None' return ','.join(args) while True: param_str, changes = re.subn(r' ?\[([^\[\]]+)\]', change_options, param_str) if changes == 0: break param_str = param_str.replace('-', '_') # see: isinstance.__doc__ # parse return value r = re.search(u'-[>-]* ', doc[end:end + 7]) if r is None: ret = u'' else: index = end + r.end() # get result type, which can contain newlines pattern = re.compile(r'(,\n|[^\n-])+') ret_str = pattern.match(doc, index).group(0).strip() # New object -> object() ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str) ret = docstr_defaults.get(ret_str, ret_str) return param_str, ret
def py__getattribute__(self, name_or_str, name_context=None, position=None, analysis_errors=True): """ :param position: Position of the last statement -> tuple of line, column """ if name_context is None: name_context = self names = self.goto(name_or_str, position) string_name = name_or_str.value if isinstance(name_or_str, Name) else name_or_str # This paragraph is currently needed for proper branch type inference # (static analysis). found_predefined_types = None if self.predefined_names and isinstance(name_or_str, Name): node = name_or_str while node is not None and not parser_utils.is_scope(node): node = node.parent if node.type in ("if_stmt", "for_stmt", "comp_for", 'sync_comp_for'): try: name_dict = self.predefined_names[node] types = name_dict[string_name] except KeyError: continue else: found_predefined_types = types break if found_predefined_types is not None and names: from medi.inference import flow_analysis check = flow_analysis.reachability_check( context=self, value_scope=self.tree_node, node=name_or_str, ) if check is flow_analysis.UNREACHABLE: values = NO_VALUES else: values = found_predefined_types else: values = ValueSet.from_sets(name.infer() for name in names) if not names and not values and analysis_errors: if isinstance(name_or_str, Name): from medi.inference import analysis message = ("NameError: name '%s' is not defined." % string_name) analysis.add(name_context, 'name-error', name_or_str, message) debug.dbg('context.names_to_types: %s -> %s', names, values) if values: return values return self._check_for_additional_knowledge(name_or_str, name_context, position)
def py__simple_getitem__(self, index): if self._is_homogenous(): return self._generics_manager.get_index_and_execute(0) else: if isinstance(index, int): return self._generics_manager.get_index_and_execute(index) debug.dbg('The getitem type on Tuple was %s' % index) return NO_VALUES
def goto(self, name_or_str, position): from medi.inference import finder filters = _get_global_filters_for_name( self, name_or_str if isinstance(name_or_str, Name) else None, position, ) names = finder.filter_name(filters, name_or_str) debug.dbg('context.goto %s in (%s): %s', name_or_str, self, names) return names
def extract_variable(inference_state, path, module_node, name, pos, until_pos): nodes = _find_nodes(module_node, pos, until_pos) debug.dbg('Extracting nodes: %s', nodes) is_expression, message = _is_expression_with_error(nodes) if not is_expression: raise RefactoringError(message) generated_code = name + ' = ' + _expression_nodes_to_string(nodes) file_to_node_changes = {path: _replace(nodes, name, generated_code, pos)} return Refactoring(inference_state, file_to_node_changes)
def iterate(self, contextualized_node=None, is_async=False): debug.dbg('iterate %s', self) if is_async: from medi.inference.lazy_value import LazyKnownValues # TODO if no __aiter__ values are there, error should be: # TypeError: 'async for' requires an object with __aiter__ method, got int return iter([ LazyKnownValues( self.py__getattribute__('__aiter__').execute_with_values( ).py__getattribute__('__anext__').execute_with_values( ).py__getattribute__('__await__').execute_with_values(). py__stop_iteration_returns()) # noqa ]) return self.py__iter__(contextualized_node)
def _infer_for_statement_string(module_context, string): code = dedent(u(""" def pseudo_docstring_stuff(): ''' Create a pseudo function for docstring statements. Need this docstring so that if the below part is not valid Python this is still a function. ''' {} """)) if string is None: return [] for element in re.findall(r'((?:\w+\.)*\w+)\.', string): # Try to import module part in dotted name. # (e.g., 'threading' in 'threading.Thread'). string = 'import %s\n' % element + string # Take the default grammar here, if we load the Python 2.7 grammar here, it # will be impossible to use `...` (Ellipsis) as a token. Docstring types # don't need to conform with the current grammar. debug.dbg('Parse docstring code %s', string, color='BLUE') grammar = module_context.inference_state.latest_grammar try: module = grammar.parse(code.format(indent_block(string)), error_recovery=False) except ParserSyntaxError: return [] try: funcdef = next(module.iter_funcdefs()) # First pick suite, then simple_stmt and then the node, # which is also not the last item, because there's a newline. stmt = funcdef.children[-1].children[-1].children[-2] except (AttributeError, IndexError): return [] if stmt.type not in ('name', 'atom', 'atom_expr'): return [] from medi.inference.value import FunctionValue function_value = FunctionValue( module_context.inference_state, module_context, funcdef ) func_execution_context = function_value.as_context() # Use the module of the param. # TODO this module is not the module of the param in case of a function # call. In that case it's the module of the function call. # stuffed with content from a function call. return list(_execute_types_in_stmt(func_execution_context, stmt))
def py__call__(self, arguments): debug.dbg("Execute overloaded function %s", self._wrapped_value, color='BLUE') function_executions = [] for signature in self.get_signatures(): function_execution = signature.value.as_context(arguments) function_executions.append(function_execution) if signature.matches_signature(arguments): return function_execution.infer() if self.inference_state.is_analysis: # In this case we want precision. return NO_VALUES return ValueSet.from_sets(fe.infer() for fe in function_executions)
def _apply_decorators(context, node): """ Returns the function, that should to be executed in the end. This is also the places where the decorators are processed. """ if node.type == 'classdef': decoratee_value = ClassValue( context.inference_state, parent_context=context, tree_node=node ) else: decoratee_value = FunctionValue.from_context(context, node) initial = values = ValueSet([decoratee_value]) if is_big_annoying_library(context): return values for dec in reversed(node.get_decorators()): debug.dbg('decorator: %s %s', dec, values, color="MAGENTA") with debug.increase_indent_cm(): dec_values = context.infer_node(dec.children[1]) trailer_nodes = dec.children[2:-1] if trailer_nodes: # Create a trailer and infer it. trailer = tree.PythonNode('trailer', trailer_nodes) trailer.parent = dec dec_values = infer_trailer(context, dec_values, trailer) if not len(dec_values): code = dec.get_code(include_prefix=False) # For the short future, we don't want to hear about the runtime # decorator in typing that was intentionally omitted. This is not # "correct", but helps with debugging. if code != '@runtime\n': debug.warning('decorator not found: %s on %s', dec, node) return initial values = dec_values.execute(arguments.ValuesArguments([values])) if not len(values): debug.warning('not possible to resolve wrappers found %s', node) return initial debug.dbg('decorator end %s', values, color="MAGENTA") if values != initial: return ValueSet([Decoratee(c, decoratee_value) for c in values]) return values
def infer_import(context, tree_name): module_context = context.get_root_context() from_import_name, import_path, level, values = \ _prepare_infer_import(module_context, tree_name) if values: if from_import_name is not None: values = values.py__getattribute__(from_import_name, name_context=context, analysis_errors=False) if not values: path = import_path + (from_import_name, ) importer = Importer(context.inference_state, path, module_context, level) values = importer.follow() debug.dbg('after import: %s', values) return values
def infer_param(function_value, param): def infer_docstring(docstring): return ValueSet( p for param_str in _search_param_in_docstr(docstring, param.name.value) for p in _infer_for_statement_string(module_context, param_str) ) module_context = function_value.get_root_context() func = param.get_parent_function() if func.type == 'lambdef': return NO_VALUES types = infer_docstring(function_value.py__doc__()) if function_value.is_bound_method() \ and function_value.py__name__() == '__init__': types |= infer_docstring(function_value.class_context.py__doc__()) debug.dbg('Found param types for docstring: %s', types, color='BLUE') return types
def dynamic_param_lookup(function_value, param_index): """ A dynamic search for param values. If you try to complete a type: >>> def func(foo): ... foo >>> func(1) >>> func("") It is not known what the type ``foo`` without analysing the whole code. You have to look for all calls to ``func`` to find out what ``foo`` possibly is. """ funcdef = function_value.tree_node if not settings.dynamic_params: return NO_VALUES path = function_value.get_root_context().py__file__() if path is not None and is_stdlib_path(path): # We don't want to search for references in the stdlib. Usually people # don't work with it (except if you are a core maintainer, sorry). # This makes everything slower. Just disable it and run the tests, # you will see the slowdown, especially in 3.6. return NO_VALUES if funcdef.type == 'lambdef': string_name = _get_lambda_name(funcdef) if string_name is None: return NO_VALUES else: string_name = funcdef.name.value debug.dbg('Dynamic param search in %s.', string_name, color='MAGENTA') module_context = function_value.get_root_context() arguments_list = _search_function_arguments(module_context, funcdef, string_name) values = ValueSet.from_sets( get_executed_param_names(function_value, arguments) [param_index].infer() for arguments in arguments_list) debug.dbg('Dynamic param result finished', color='MAGENTA') return values
def definition(correct, correct_start, path): should_be = set() for match in re.finditer('(?:[^ ]+)', correct): string = match.group(0) parser = grammar37.parse(string, start_symbol='eval_input', error_recovery=False) parser_utils.move(parser.get_root_node(), self.line_nr) node = parser.get_root_node() module_context = script._get_module_context() user_context = get_user_context(module_context, (self.line_nr, 0)) node.parent = user_context.tree_node results = convert_values(user_context.infer_node(node)) if not results: raise Exception('Could not resolve %s on line %s' % (match.string, self.line_nr - 1)) should_be |= set(Name(inference_state, r.name) for r in results) debug.dbg('Finished getting types', color='YELLOW') # Because the objects have different ids, `repr`, then compare. should = set(comparison(r) for r in should_be) return should
def matches_signature(self): if self._is_default: return True argument_values = self.infer().py__class__() if self.get_kind() in (Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD): return True annotations = self.infer_annotation(execute_annotation=False) if not annotations: # If we cannot infer annotations - or there aren't any - pretend # that the signature matches. return True matches = any( c1.is_sub_class_of(c2) for c1 in argument_values for c2 in annotations.gather_annotation_classes()) debug.dbg("param compare %s: %s <=> %s", matches, argument_values, annotations, color='BLUE') return matches
def search_in_file_ios(inference_state, file_io_iterator, name, limit_reduction=1): parse_limit = _PARSED_FILE_LIMIT / limit_reduction open_limit = _OPENED_FILE_LIMIT / limit_reduction file_io_count = 0 parsed_file_count = 0 regex = re.compile(r'\b' + re.escape(name) + r'\b') for file_io in file_io_iterator: file_io_count += 1 m = _check_fs(inference_state, file_io, regex) if m is not None: parsed_file_count += 1 yield m if parsed_file_count >= parse_limit: dbg('Hit limit of parsed files: %s', parse_limit) break if file_io_count >= open_limit: dbg('Hit limit of opened files: %s', open_limit) break
def infer_trailer(context, atom_values, trailer): trailer_op, node = trailer.children[:2] if node == ')': # `arglist` is optional. node = None if trailer_op == '[': trailer_op, node, _ = trailer.children return atom_values.get_item( _infer_subscript_list(context, node), ContextualizedNode(context, trailer) ) else: debug.dbg('infer_trailer: %s in %s', trailer, atom_values) if trailer_op == '.': return atom_values.py__getattribute__( name_context=context, name_or_str=node ) else: assert trailer_op == '(', 'trailer_op is actually %s' % trailer_op args = arguments.TreeArguments(context.inference_state, context, node, trailer) return atom_values.execute(args)
def get_return_values(self, check_yields=False): funcdef = self.tree_node if funcdef.type == 'lambdef': return self.infer_node(funcdef.children[-1]) if check_yields: value_set = NO_VALUES returns = get_yield_exprs(self.inference_state, funcdef) else: value_set = self._infer_annotations() if value_set: # If there are annotations, prefer them over anything else. # This will make it faster. return value_set value_set |= docstrings.infer_return_types(self._value) returns = funcdef.iter_return_stmts() for r in returns: if check_yields: value_set |= ValueSet.from_sets( lazy_value.infer() for lazy_value in self._get_yield_lazy_value(r)) else: check = flow_analysis.reachability_check(self, funcdef, r) if check is flow_analysis.UNREACHABLE: debug.dbg('Return unreachable: %s', r) else: try: children = r.children except AttributeError: ctx = compiled.builtin_from_name( self.inference_state, u'None') value_set |= ValueSet([ctx]) else: value_set |= self.infer_node(children[1]) if check is flow_analysis.REACHABLE: debug.dbg('Return reachable: %s', r) break return value_set
def _getitem(value, index_values, contextualized_node): # The actual getitem call. result = NO_VALUES unused_values = set() for index_value in index_values: index = index_value.get_safe_value(default=None) if type(index) in (float, int, str, unicode, slice, bytes): try: result |= value.py__simple_getitem__(index) continue except SimpleGetItemNotFound: pass unused_values.add(index_value) # The index was somehow not good enough or simply a wrong type. # Therefore we now iterate through all the values and just take # all results. if unused_values or not index_values: result |= value.py__getitem__(ValueSet(unused_values), contextualized_node) debug.dbg('py__getitem__ result: %s', result) return result
def infer_or_test(context, or_test): iterator = iter(or_test.children) types = context.infer_node(next(iterator)) for operator in iterator: right = next(iterator) if operator.type == 'comp_op': # not in / is not operator = ' '.join(c.value for c in operator.children) # handle type inference of and/or here. if operator in ('and', 'or'): left_bools = set(left.py__bool__() for left in types) if left_bools == {True}: if operator == 'and': types = context.infer_node(right) elif left_bools == {False}: if operator != 'and': types = context.infer_node(right) # Otherwise continue, because of uncertainty. else: types = _infer_comparison(context, types, operator, context.infer_node(right)) debug.dbg('infer_or_test types %s', types) return types
def py__getattribute__(self, name_or_str, name_context=None, position=None, analysis_errors=True): """ :param position: Position of the last statement -> tuple of line, column """ if name_context is None: name_context = self names = self.goto(name_or_str, name_context, analysis_errors) values = ValueSet.from_sets(name.infer() for name in names) if not values: n = name_or_str.value if isinstance(name_or_str, Name) else name_or_str values = self.py__getattribute__alternatives(n) if not names and not values and analysis_errors: if isinstance(name_or_str, Name): from medi.inference import analysis analysis.add_attribute_error(name_context, self, name_or_str) debug.dbg('context.names_to_types: %s -> %s', names, values) return values
def goto(self, name_or_str, name_context=None, analysis_errors=True): from medi.inference import finder filters = self._get_value_filters(name_or_str) names = finder.filter_name(filters, name_or_str) debug.dbg('context.goto %s in (%s): %s', name_or_str, self, names) return names
def _internal_check_array_additions(context, sequence): """ Checks if a `Array` has "add" (append, insert, extend) statements: >>> a = [""] >>> a.append(1) """ from medi.inference import arguments debug.dbg('Dynamic array search for %s' % sequence, color='MAGENTA') module_context = context.get_root_context() if not settings.dynamic_array_additions or module_context.is_compiled(): debug.dbg('Dynamic array search aborted.', color='MAGENTA') return NO_VALUES def find_additions(context, arglist, add_name): params = list( arguments.TreeArguments(context.inference_state, context, arglist).unpack()) result = set() if add_name in ['insert']: params = params[1:] if add_name in ['append', 'add', 'insert']: for key, lazy_value in params: result.add(lazy_value) elif add_name in ['extend', 'update']: for key, lazy_value in params: result |= set(lazy_value.infer().iterate()) return result temp_param_add, settings.dynamic_params_for_other_modules = \ settings.dynamic_params_for_other_modules, False is_list = sequence.name.string_name == 'list' search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update']) added_types = set() for add_name in search_names: try: possible_names = module_context.tree_node.get_used_names( )[add_name] except KeyError: continue else: for name in possible_names: value_node = context.tree_node if not (value_node.start_pos < name.start_pos < value_node.end_pos): continue trailer = name.parent power = trailer.parent trailer_pos = power.children.index(trailer) try: execution_trailer = power.children[trailer_pos + 1] except IndexError: continue else: if execution_trailer.type != 'trailer' \ or execution_trailer.children[0] != '(' \ or execution_trailer.children[1] == ')': continue random_context = context.create_context(name) with recursion.execution_allowed(context.inference_state, power) as allowed: if allowed: found = infer_call_of_leaf(random_context, name, cut_own_trailer=True) if sequence in found: # The arrays match. Now add the results added_types |= find_additions( random_context, execution_trailer.children[1], add_name) # reset settings settings.dynamic_params_for_other_modules = temp_param_add debug.dbg('Dynamic array result %s', added_types, color='MAGENTA') return added_types
def test_simple(): medi.set_debug_function() debug.speed('foo') debug.dbg('bar') debug.warning('baz') medi.set_debug_function(None, False, False, False)
def execute(value, arguments): debug.dbg('execute: %s %s', value, arguments) with debug.increase_indent_cm(): value_set = value.py__call__(arguments=arguments) debug.dbg('execute result: %s in %s', value_set, value) return value_set
def infer_node(context, element): if isinstance(context, CompForContext): return _infer_node(context, element) if_stmt = element while if_stmt is not None: if_stmt = if_stmt.parent if if_stmt.type in ('if_stmt', 'for_stmt'): break if parser_utils.is_scope(if_stmt): if_stmt = None break predefined_if_name_dict = context.predefined_names.get(if_stmt) # TODO there's a lot of issues with this one. We actually should do # this in a different way. Caching should only be active in certain # cases and this all sucks. if predefined_if_name_dict is None and if_stmt \ and if_stmt.type == 'if_stmt' and context.inference_state.is_analysis: if_stmt_test = if_stmt.children[1] name_dicts = [{}] # If we already did a check, we don't want to do it again -> If # value.predefined_names is filled, we stop. # We don't want to check the if stmt itself, it's just about # the content. if element.start_pos > if_stmt_test.end_pos: # Now we need to check if the names in the if_stmt match the # names in the suite. if_names = get_names_of_node(if_stmt_test) element_names = get_names_of_node(element) str_element_names = [e.value for e in element_names] if any(i.value in str_element_names for i in if_names): for if_name in if_names: definitions = context.inference_state.infer(context, if_name) # Every name that has multiple different definitions # causes the complexity to rise. The complexity should # never fall below 1. if len(definitions) > 1: if len(name_dicts) * len(definitions) > 16: debug.dbg('Too many options for if branch inference %s.', if_stmt) # There's only a certain amount of branches # Medi can infer, otherwise it will take to # long. name_dicts = [{}] break original_name_dicts = list(name_dicts) name_dicts = [] for definition in definitions: new_name_dicts = list(original_name_dicts) for i, name_dict in enumerate(new_name_dicts): new_name_dicts[i] = name_dict.copy() new_name_dicts[i][if_name.value] = ValueSet([definition]) name_dicts += new_name_dicts else: for name_dict in name_dicts: name_dict[if_name.value] = definitions if len(name_dicts) > 1: result = NO_VALUES for name_dict in name_dicts: with context.predefine_names(if_stmt, name_dict): result |= _infer_node(context, element) return result else: return _infer_node_if_inferred(context, element) else: if predefined_if_name_dict: return _infer_node(context, element) else: return _infer_node_if_inferred(context, element)
def _infer_comparison_part(inference_state, context, left, operator, right): l_is_num = is_number(left) r_is_num = is_number(right) if isinstance(operator, unicode): str_operator = operator else: str_operator = force_unicode(str(operator.value)) if str_operator == '*': # for iterables, ignore * operations if isinstance(left, iterable.Sequence) or is_string(left): return ValueSet([left]) elif isinstance(right, iterable.Sequence) or is_string(right): return ValueSet([right]) elif str_operator == '+': if l_is_num and r_is_num or is_string(left) and is_string(right): return left.execute_operation(right, str_operator) elif _is_list(left) and _is_list(right) or _is_tuple(left) and _is_tuple(right): return ValueSet([iterable.MergedArray(inference_state, (left, right))]) elif str_operator == '-': if l_is_num and r_is_num: return left.execute_operation(right, str_operator) elif str_operator == '%': # With strings and numbers the left type typically remains. Except for # `int() % float()`. return ValueSet([left]) elif str_operator in COMPARISON_OPERATORS: if left.is_compiled() and right.is_compiled(): # Possible, because the return is not an option. Just compare. result = left.execute_operation(right, str_operator) if result: return result else: if str_operator in ('is', '!=', '==', 'is not'): operation = COMPARISON_OPERATORS[str_operator] bool_ = operation(left, right) # Only if == returns True or != returns False, we can continue. # There's no guarantee that they are not equal. This can help # in some cases, but does not cover everything. if (str_operator in ('is', '==')) == bool_: return ValueSet([_bool_to_value(inference_state, bool_)]) if isinstance(left, VersionInfo): version_info = _get_tuple_ints(right) if version_info is not None: bool_result = compiled.access.COMPARISON_OPERATORS[operator]( inference_state.environment.version_info, tuple(version_info) ) return ValueSet([_bool_to_value(inference_state, bool_result)]) return ValueSet([ _bool_to_value(inference_state, True), _bool_to_value(inference_state, False) ]) elif str_operator in ('in', 'not in'): return NO_VALUES def check(obj): """Checks if a Medi object is either a float or an int.""" return isinstance(obj, TreeInstance) and \ obj.name.string_name in ('int', 'float') # Static analysis, one is a number, the other one is not. if str_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)) if left.is_class() or right.is_class(): return NO_VALUES method_name = operator_to_magic_method[str_operator] magic_methods = left.py__getattribute__(method_name) if magic_methods: result = magic_methods.execute_with_values(right) if result: return result if not magic_methods: reverse_method_name = reverse_operator_to_magic_method[str_operator] magic_methods = right.py__getattribute__(reverse_method_name) result = magic_methods.execute_with_values(left) if result: return result result = ValueSet([left, right]) debug.dbg('Used operator %s resulting in %s', operator, result) return result
def _infer_expr_stmt(context, stmt, seek_name=None): """ The starting point of the completion. A statement always owns a call list, which are the calls, that a statement does. In case multiple names are defined in the statement, `seek_name` returns the result for this name. expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) | ('=' (yield_expr|testlist_star_expr))*) annassign: ':' test ['=' test] augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//=') :param stmt: A `tree.ExprStmt`. """ def check_setitem(stmt): atom_expr = stmt.children[0] if atom_expr.type not in ('atom_expr', 'power'): return False, None name = atom_expr.children[0] if name.type != 'name' or len(atom_expr.children) != 2: return False, None trailer = atom_expr.children[-1] return trailer.children[0] == '[', trailer.children[1] debug.dbg('infer_expr_stmt %s (%s)', stmt, seek_name) rhs = stmt.get_rhs() value_set = context.infer_node(rhs) if seek_name: n = TreeNameDefinition(context, seek_name) value_set = check_tuple_assignments(n, value_set) first_operator = next(stmt.yield_operators(), None) is_setitem, subscriptlist = check_setitem(stmt) is_annassign = first_operator not in ('=', None) and first_operator.type == 'operator' if is_annassign or is_setitem: # `=` is always the last character in aug assignments -> -1 name = stmt.get_defined_names(include_setitem=True)[0].value left_values = context.py__getattribute__(name, position=stmt.start_pos) if is_setitem: def to_mod(v): c = ContextualizedSubscriptListNode(context, subscriptlist) if v.array_type == 'dict': return DictModification(v, value_set, c) elif v.array_type == 'list': return ListModification(v, value_set, c) return v value_set = ValueSet(to_mod(v) for v in left_values) else: operator = copy.copy(first_operator) operator.value = operator.value[:-1] for_stmt = tree.search_ancestor(stmt, 'for_stmt') if for_stmt is not None and for_stmt.type == 'for_stmt' and value_set \ and parser_utils.for_stmt_defines_one_name(for_stmt): # Iterate through result and add the values, that's possible # only in for loops without clutter, because they are # predictable. Also only do it, if the variable is not a tuple. node = for_stmt.get_testlist() cn = ContextualizedNode(context, node) ordered = list(cn.infer().iterate(cn)) for lazy_value in ordered: dct = {for_stmt.children[1].value: lazy_value.infer()} with context.predefine_names(for_stmt, dct): t = context.infer_node(rhs) left_values = _infer_comparison(context, left_values, operator, t) value_set = left_values else: value_set = _infer_comparison(context, left_values, operator, value_set) debug.dbg('infer_expr_stmt result %s', value_set) return value_set
def _infer_node(context, element): debug.dbg('infer_node %s@%s in %s', element, element.start_pos, context) inference_state = context.inference_state typ = element.type if typ in ('name', 'number', 'string', 'atom', 'strings', 'keyword', 'fstring'): return infer_atom(context, element) elif typ == 'lambdef': return ValueSet([FunctionValue.from_context(context, element)]) elif typ == 'expr_stmt': return infer_expr_stmt(context, element) elif typ in ('power', 'atom_expr'): first_child = element.children[0] children = element.children[1:] had_await = False if first_child.type == 'keyword' and first_child.value == 'await': had_await = True first_child = children.pop(0) value_set = context.infer_node(first_child) for (i, trailer) in enumerate(children): if trailer == '**': # has a power operation. right = context.infer_node(children[i + 1]) value_set = _infer_comparison( context, value_set, trailer, right ) break value_set = infer_trailer(context, value_set, trailer) if had_await: return value_set.py__await__().py__stop_iteration_returns() return value_set elif typ in ('testlist_star_expr', 'testlist',): # The implicit tuple in statements. return ValueSet([iterable.SequenceLiteralValue(inference_state, context, element)]) elif typ in ('not_test', 'factor'): value_set = context.infer_node(element.children[-1]) for operator in element.children[:-1]: value_set = infer_factor(value_set, operator) return value_set elif typ == 'test': # `x if foo else y` case. return (context.infer_node(element.children[0]) | context.infer_node(element.children[-1])) elif typ == 'operator': # Must be an ellipsis, other operators are not inferred. # In Python 2 ellipsis is coded as three single dot tokens, not # as one token 3 dot token. if element.value not in ('.', '...'): origin = element.parent raise AssertionError("unhandled operator %s in %s " % (repr(element.value), origin)) return ValueSet([compiled.builtin_from_name(inference_state, u'Ellipsis')]) elif typ == 'dotted_name': value_set = infer_atom(context, element.children[0]) for next_name in element.children[2::2]: value_set = value_set.py__getattribute__(next_name, name_context=context) return value_set elif typ == 'eval_input': return context.infer_node(element.children[0]) elif typ == 'annassign': return annotation.infer_annotation(context, element.children[1]) \ .execute_annotation() elif typ == 'yield_expr': if len(element.children) and element.children[1].type == 'yield_arg': # Implies that it's a yield from. element = element.children[1].children[1] generators = context.infer_node(element) \ .py__getattribute__('__iter__').execute_with_values() return generators.py__stop_iteration_returns() # Generator.send() is not implemented. return NO_VALUES elif typ == 'namedexpr_test': return context.infer_node(element.children[2]) else: return infer_or_test(context, element)