# Copyright (c) 2015-2016 Cara Vinson # Copyright (c) 2015-2016 Claudiu Popa # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html # For details: https://github.com/PyCQA/astroid/blob/master/COPYING.LESSER """ Various helper utilities. """ import six from astroid import bases from astroid import context as contextmod from astroid import exceptions from astroid import manager from astroid import nodes from astroid import raw_building from astroid import scoped_nodes from astroid import util BUILTINS = six.moves.builtins.__name__ def _build_proxy_class(cls_name, builtins): proxy = raw_building.build_class(cls_name) proxy.parent = builtins return proxy def _function_type(function, builtins): if isinstance(function, scoped_nodes.Lambda): if function.root().name == BUILTINS: cls_name = 'builtin_function_or_method' else: cls_name = 'function' elif isinstance(function, bases.BoundMethod): if six.PY2: cls_name = 'instancemethod' else: cls_name = 'method' elif isinstance(function, bases.UnboundMethod): if six.PY2: cls_name = 'instancemethod' else: cls_name = 'function' return _build_proxy_class(cls_name, builtins) def _object_type(node, context=None): astroid_manager = manager.AstroidManager() builtins = astroid_manager.astroid_cache[BUILTINS] context = context or contextmod.InferenceContext() for inferred in node.infer(context=context): if isinstance(inferred, scoped_nodes.ClassDef): if inferred.newstyle: metaclass = inferred.metaclass() if metaclass: yield metaclass continue yield builtins.getattr('type')[0] elif isinstance(inferred, (scoped_nodes.Lambda, bases.UnboundMethod)): yield _function_type(inferred, builtins) elif isinstance(inferred, scoped_nodes.Module): yield _build_proxy_class('module', builtins) else: yield inferred._proxied def object_type(node, context=None): """Obtain the type of the given node This is used to implement the ``type`` builtin, which means that it's used for inferring type calls, as well as used in a couple of other places in the inference. The node will be inferred first, so this function can support all sorts of objects, as long as they support inference. """ try: types = set(_object_type(node, context)) except exceptions.InferenceError: return util.Uninferable if len(types) > 1 or not types: return util.Uninferable return list(types)[0] def safe_infer(node, context=None): """Return the inferred value for the given node. Return None if inference failed or if there is some ambiguity (more than one node has been inferred). """ try: inferit = node.infer(context=context) value = next(inferit) except exceptions.InferenceError: return try: next(inferit) return # None if there is ambiguity on the inferred node except exceptions.InferenceError: return # there is some kind of ambiguity except StopIteration: return value def has_known_bases(klass, context=None): """Return true if all base classes of a class could be inferred.""" try: return klass._all_bases_known except AttributeError: pass for base in klass.bases: result = safe_infer(base, context=context) # TODO: check for A->B->A->B pattern in class structure too? if (not isinstance(result, scoped_nodes.ClassDef) or result is klass or not has_known_bases(result, context=context)): klass._all_bases_known = False return False klass._all_bases_known = True return True def _type_check(type1, type2): if not all(map(has_known_bases, (type1, type2))): raise exceptions._NonDeducibleTypeHierarchy if not all([type1.newstyle, type2.newstyle]): return False try: return type1 in type2.mro()[:-1] except exceptions.MroError: # The MRO is invalid. raise exceptions._NonDeducibleTypeHierarchy def is_subtype(type1, type2): """Check if *type1* is a subtype of *typ2*.""" return _type_check(type2, type1) def is_supertype(type1, type2): """Check if *type2* is a supertype of *type1*.""" return _type_check(type1, type2) def class_instance_as_index(node): """Get the value as an index for the given instance. If an instance provides an __index__ method, then it can be used in some scenarios where an integer is expected, for instance when multiplying or subscripting a list. """ context = contextmod.InferenceContext() context.callcontext = contextmod.CallContext(args=[node]) try: for inferred in node.igetattr('__index__', context=context): if not isinstance(inferred, bases.BoundMethod): continue for result in inferred.infer_call_result(node, context=context): if (isinstance(result, nodes.Const) and isinstance(result.value, int)): return result except exceptions.InferenceError: pass