def infer_return_types(function, arguments): """ Infers the type of a function's return value, according to type annotations. """ all_annotations = py__annotations__(function.tree_node) annotation = all_annotations.get("return", None) if annotation is None: # If there is no Python 3-type annotation, look for a Python 2-type annotation node = function.tree_node comment = parser_utils.get_following_comment_same_line(node) if comment is None: return NO_VALUES match = re.match(r"^#\s*type:\s*\([^#]*\)\s*->\s*([^#]*)", comment) if not match: return NO_VALUES return _infer_annotation_string( function.get_default_param_context(), match.group(1).strip()).execute_annotation() context = function.get_default_param_context() unknown_type_vars = find_unknown_type_vars(context, annotation) annotation_values = infer_annotation(context, annotation) if not unknown_type_vars: return annotation_values.execute_annotation() type_var_dict = infer_type_vars_for_execution(function, arguments, all_annotations) return ValueSet.from_sets( ann.define_generics(type_var_dict) if isinstance( ann, (DefineGenericBaseClass, TypeVar)) else ValueSet({ann}) for ann in annotation_values).execute_annotation()
def py__get__(self, instance, class_value): from medi.inference.value.instance import BoundMethod if instance is None: # Calling the Foo.bar results in the original bar function. return ValueSet([self]) return ValueSet( [BoundMethod(instance, class_value.as_context(), self)])
def _infer_subscript_list(context, index): """ Handles slices in subscript nodes. """ if index == ':': # Like array[:] return ValueSet([iterable.Slice(context, None, None, None)]) elif index.type == 'subscript' and not index.children[0] == '.': # subscript basically implies a slice operation, except for Python 2's # Ellipsis. # e.g. array[:3] result = [] for el in index.children: if el == ':': if not result: result.append(None) elif el.type == 'sliceop': if len(el.children) == 2: result.append(el.children[1]) else: result.append(el) result += [None] * (3 - len(result)) return ValueSet([iterable.Slice(context, *result)]) elif index.type == 'subscriptlist': return ValueSet([iterable.SequenceLiteralValue(context.inference_state, context, index)]) # No slices return context.infer_node(index)
def execute_annotation(self): string_name = self._tree_name.value if string_name == 'Union': # This is kind of a special case, because we have Unions (in Medi # ValueSets). return self.gather_annotation_classes().execute_annotation() elif string_name == 'Optional': # Optional is basically just saying it's either None or the actual # type. return self.gather_annotation_classes().execute_annotation() \ | ValueSet([builtin_from_name(self.inference_state, u'None')]) elif string_name == 'Type': # The type is actually already given in the index_value return self._generics_manager[0] elif string_name == 'ClassVar': # For now don't do anything here, ClassVars are always used. return self._generics_manager[0].execute_annotation() mapped = { 'Tuple': Tuple, 'Generic': Generic, 'Protocol': Protocol, 'Callable': Callable, } cls = mapped[string_name] return ValueSet([ cls( self.parent_context, self, self._tree_name, generics_manager=self._generics_manager, ) ])
def define_generics(self, type_var_dict): from medi.inference.gradual.type_var import TypeVar changed = False new_generics = [] for generic_set in self.get_generics(): values = NO_VALUES for generic in generic_set: if isinstance(generic, (DefineGenericBaseClass, TypeVar)): result = generic.define_generics(type_var_dict) values |= result if result != ValueSet({generic}): changed = True else: values |= ValueSet([generic]) new_generics.append(values) if not changed: # There might not be any type vars that change. In that case just # return itself, because it does not make sense to potentially lose # cached results. return ValueSet([self]) return ValueSet([ self._create_instance_with_generics( TupleGenericManager(tuple(new_generics))) ])
def py__call__(self, arguments=None): from medi.inference.value import TreeInstance from medi.inference.gradual.typing import TypedDict if self.is_typeddict(): return ValueSet([TypedDict(self)]) return ValueSet([ TreeInstance(self.inference_state, self.parent_context, self, arguments) ])
def py__getitem__(self, index_value_set, contextualized_node): from medi.inference.gradual.base import GenericClass if not index_value_set: return ValueSet([self]) return ValueSet( GenericClass( self, LazyGenericManager( context_of_index=contextualized_node.context, index_value=index_value, )) for index_value in index_value_set)
def infer_return_for_callable(arguments, param_values, result_values): all_type_vars = {} for pv in param_values: if pv.array_type == 'list': type_var_dict = _infer_type_vars_for_callable( arguments, pv.py__iter__()) all_type_vars.update(type_var_dict) return ValueSet.from_sets( v.define_generics(all_type_vars) if isinstance(v, ( DefineGenericBaseClass, TypeVar)) else ValueSet({v}) for v in result_values).execute_annotation()
def _tuple(self): def lambda_scoping_in_for_loop_sucks(lazy_value): return lambda: ValueSet(_resolve_forward_references( self._context_of_index, lazy_value.infer() )) if isinstance(self._index_value, SequenceLiteralValue): for lazy_value in self._index_value.py__iter__(contextualized_node=None): yield lambda_scoping_in_for_loop_sucks(lazy_value) else: yield lambda: ValueSet(_resolve_forward_references( self._context_of_index, ValueSet([self._index_value]) ))
def _remap_type_vars(self, base): from medi.inference.gradual.type_var import TypeVar filter = self._class_value.get_type_var_filter() for type_var_set in base.get_generics(): new = NO_VALUES for type_var in type_var_set: if isinstance(type_var, TypeVar): names = filter.get(type_var.py__name__()) new |= ValueSet.from_sets(name.infer() for name in names) else: # Mostly will be type vars, except if in some cases # a concrete type will already be there. In that # case just add it to the value set. new |= ValueSet([type_var]) yield new
def load_module_from_path(inference_state, file_io, import_names=None, is_package=None): """ This should pretty much only be used for get_modules_containing_name. It's here to ensure that a random path is still properly loaded into the Medi module structure. """ path = file_io.path if import_names is None: e_sys_path = inference_state.get_sys_path() import_names, is_package = sys_path.transform_path_to_dotted( e_sys_path, path) else: assert isinstance(is_package, bool) is_stub = file_io.path.endswith('.pyi') if is_stub: folder_io = file_io.get_parent_folder() if folder_io.path.endswith('-stubs'): folder_io = FolderIO(folder_io.path[:-6]) if file_io.path.endswith('__init__.pyi'): python_file_io = folder_io.get_file_io('__init__.py') else: python_file_io = folder_io.get_file_io(import_names[-1] + '.py') try: v = load_module_from_path(inference_state, python_file_io, import_names, is_package=is_package) values = ValueSet([v]) except FileNotFoundError: values = NO_VALUES return create_stub_module(inference_state, values, parse_stub_module(inference_state, file_io), file_io, import_names) else: module = _load_python_module( inference_state, file_io, import_names=import_names, is_package=is_package, ) inference_state.module_cache.add(import_names, ValueSet([module])) return module
def convert_values(values, only_stubs=False, prefer_stubs=False, ignore_compiled=True): assert not (only_stubs and prefer_stubs) with debug.increase_indent_cm('convert values'): if only_stubs or prefer_stubs: return ValueSet.from_sets( to_stub(value) or ( ValueSet({value}) if prefer_stubs else NO_VALUES) for value in values) else: return ValueSet.from_sets( _stub_to_python_value_set(stub_value, ignore_compiled=ignore_compiled) or ValueSet({stub_value}) for stub_value in values)
def infer(self, context, name): def_ = name.get_definition(import_name_always=True) if def_ is not None: type_ = def_.type is_classdef = type_ == 'classdef' if is_classdef or type_ == 'funcdef': if is_classdef: c = ClassValue(self, context, name.parent) else: c = FunctionValue.from_context(context, name.parent) return ValueSet([c]) if type_ == 'expr_stmt': is_simple_name = name.parent.type not in ('power', 'trailer') if is_simple_name: return infer_expr_stmt(context, def_, name) if type_ == 'for_stmt': container_types = context.infer_node(def_.children[3]) cn = ContextualizedNode(context, def_.children[3]) for_types = iterate_values(container_types, cn) n = TreeNameDefinition(context, name) return check_tuple_assignments(n, for_types) if type_ in ('import_from', 'import_name'): return imports.infer_import(context, name) if type_ == 'with_stmt': return tree_name_to_values(self, context, name) elif type_ == 'param': return context.py__getattribute__(name.value, position=name.end_pos) else: result = follow_error_node_imports_if_possible(context, name) if result is not None: return result return helpers.infer_call_of_leaf(context, name)
def infer(self): inferred = super(StubName, self).infer() if self.string_name == 'version_info' and self.get_root_context( ).py__name__() == 'sys': from medi.inference.gradual.stub_value import VersionInfo return ValueSet(VersionInfo(c) for c in inferred) return inferred
def import_module_by_names(inference_state, import_names, sys_path=None, module_context=None, prefer_stubs=True): if sys_path is None: sys_path = inference_state.get_sys_path() str_import_names = tuple( force_unicode(i.value if isinstance(i, tree.Name) else i) for i in import_names) value_set = [None] for i, name in enumerate(import_names): value_set = ValueSet.from_sets([ import_module( inference_state, str_import_names[:i + 1], parent_module_value, sys_path, prefer_stubs=prefer_stubs, ) for parent_module_value in value_set ]) if not value_set: message = 'No module named ' + '.'.join(str_import_names) if module_context is not None: _add_error(module_context, name, message) else: debug.warning(message) return NO_VALUES return value_set
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 get_key_values(self): values = NO_VALUES if self.array_type == 'dict': for i, (key, instance) in enumerate(self._arguments.unpack()): if key is None and i == 0: values |= ValueSet.from_sets(v.get_key_values() for v in instance.infer() if v.array_type == 'dict') if key: values |= ValueSet([ compiled.create_simple_object( self.inference_state, key, ) ]) return values
def py__call__(self, arguments): names = self.get_function_slot_names(u'__call__') if not names: # Means the Instance is not callable. return super(_BaseTreeInstance, self).py__call__(arguments) return ValueSet.from_sets(name.infer().execute(arguments) for name in names)
def get_key_values(self): filtered_values = itertools.chain.from_iterable( (f.values() for f in self._definition_class.get_filters(is_instance=True))) return ValueSet({ create_simple_object(self.inference_state, v.string_name) for v in filtered_values })
def get_metaclasses(self): args = self._get_bases_arguments() if args is not None: m = [value for key, value in args.unpack() if key == 'metaclass'] metaclasses = ValueSet.from_sets(lazy_value.infer() for lazy_value in m) metaclasses = ValueSet(m for m in metaclasses if m.is_class()) if metaclasses: return metaclasses for lazy_base in self.py__bases__(): for value in lazy_base.infer(): if value.is_class(): values = value.get_metaclasses() if values: return values return NO_VALUES
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 define_generics(self, type_var_dict): try: found = type_var_dict[self.py__name__()] except KeyError: pass else: if found: return found return self._get_classes() or ValueSet({self})
def py__getitem__(self, index_value_set, contextualized_node): names = self.get_function_slot_names(u'__getitem__') if not names: return super(_BaseTreeInstance, self).py__getitem__( index_value_set, contextualized_node, ) args = ValuesArguments([index_value_set]) return ValueSet.from_sets(name.infer().execute(args) for name in names)
def py__getitem__(self, index_value_set, contextualized_node): return ValueSet( self.index_class.create_cached( self.inference_state, self.parent_context, self._tree_name, generics_manager=LazyGenericManager( context_of_index=contextualized_node.context, index_value=index_value, )) for index_value in index_value_set)
def execute_operation(self, other, operator): try: return ValueSet([ create_from_access_path( self.inference_state, self.access_handle.execute_operation( other.access_handle, operator)) ]) except TypeError: return NO_VALUES
def _load_stub_module(module): if module.is_stub(): return module return try_to_load_stub_cached( module.inference_state, import_names=module.string_names, python_value_set=ValueSet([module]), parent_module_value=None, sys_path=module.inference_state.get_sys_path(), )
def infer(self): if self._string_value is not None: s = self._string_value if self.parent_context.inference_state.environment.version_info.major == 2 \ and not isinstance(s, bytes): s = s.encode('utf-8') return ValueSet([ create_simple_object(self.parent_context.inference_state, s) ]) return compiled.get_string_value_set(self.parent_context.inference_state)
def infer(self): compiled_value = self._wrapped_name.infer_compiled_value() tree_value = self._parent_tree_value if tree_value.is_instance() or tree_value.is_class(): tree_values = tree_value.py__getattribute__(self.string_name) if compiled_value.is_function(): return ValueSet({MixedObject(compiled_value, v) for v in tree_values}) module_context = tree_value.get_root_context() return _create(self._inference_state, compiled_value, module_context)
def py__call__(self, arguments): return_annotation = self.access_handle.get_return_annotation() if return_annotation is not None: # TODO the return annotation may also be a string. return create_from_access_path( self.inference_state, return_annotation).execute_annotation() try: self.access_handle.getattr_paths(u'__call__') except AttributeError: return super(CompiledValue, self).py__call__(arguments) else: if self.access_handle.is_class(): from medi.inference.value import CompiledInstance return ValueSet([ CompiledInstance(self.inference_state, self.parent_context, self, arguments) ]) else: return ValueSet(self._execute_function(arguments))
def infer(self): p = self._signature_param inference_state = self.parent_context.inference_state values = NO_VALUES if p.has_default: values = ValueSet( [create_from_access_path(inference_state, p.default)]) if p.has_annotation: annotation = create_from_access_path(inference_state, p.annotation) values |= annotation.execute_with_values() return values