| Index: third_party/logilab/astroid/brain/builtin_inference.py
|
| diff --git a/third_party/logilab/astroid/brain/builtin_inference.py b/third_party/logilab/astroid/brain/builtin_inference.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..f60e7913b364979a0b7ca35339b9814533840abc
|
| --- /dev/null
|
| +++ b/third_party/logilab/astroid/brain/builtin_inference.py
|
| @@ -0,0 +1,245 @@
|
| +"""Astroid hooks for various builtins."""
|
| +
|
| +import sys
|
| +from functools import partial
|
| +from textwrap import dedent
|
| +
|
| +import six
|
| +from astroid import (MANAGER, UseInferenceDefault,
|
| + inference_tip, YES, InferenceError, UnresolvableName)
|
| +from astroid import nodes
|
| +from astroid.builder import AstroidBuilder
|
| +
|
| +
|
| +def _extend_str(class_node, rvalue):
|
| + """function to extend builtin str/unicode class"""
|
| + # TODO(cpopa): this approach will make astroid to believe
|
| + # that some arguments can be passed by keyword, but
|
| + # unfortunately, strings and bytes don't accept keyword arguments.
|
| + code = dedent('''
|
| + class whatever(object):
|
| + def join(self, iterable):
|
| + return {rvalue}
|
| + def replace(self, old, new, count=None):
|
| + return {rvalue}
|
| + def format(self, *args, **kwargs):
|
| + return {rvalue}
|
| + def encode(self, encoding='ascii', errors=None):
|
| + return ''
|
| + def decode(self, encoding='ascii', errors=None):
|
| + return u''
|
| + def capitalize(self):
|
| + return {rvalue}
|
| + def title(self):
|
| + return {rvalue}
|
| + def lower(self):
|
| + return {rvalue}
|
| + def upper(self):
|
| + return {rvalue}
|
| + def swapcase(self):
|
| + return {rvalue}
|
| + def index(self, sub, start=None, end=None):
|
| + return 0
|
| + def find(self, sub, start=None, end=None):
|
| + return 0
|
| + def count(self, sub, start=None, end=None):
|
| + return 0
|
| + def strip(self, chars=None):
|
| + return {rvalue}
|
| + def lstrip(self, chars=None):
|
| + return {rvalue}
|
| + def rstrip(self, chars=None):
|
| + return {rvalue}
|
| + def rjust(self, width, fillchar=None):
|
| + return {rvalue}
|
| + def center(self, width, fillchar=None):
|
| + return {rvalue}
|
| + def ljust(self, width, fillchar=None):
|
| + return {rvalue}
|
| + ''')
|
| + code = code.format(rvalue=rvalue)
|
| + fake = AstroidBuilder(MANAGER).string_build(code)['whatever']
|
| + for method in fake.mymethods():
|
| + class_node.locals[method.name] = [method]
|
| + method.parent = class_node
|
| +
|
| +def extend_builtins(class_transforms):
|
| + from astroid.bases import BUILTINS
|
| + builtin_ast = MANAGER.astroid_cache[BUILTINS]
|
| + for class_name, transform in class_transforms.items():
|
| + transform(builtin_ast[class_name])
|
| +
|
| +if sys.version_info > (3, 0):
|
| + extend_builtins({'bytes': partial(_extend_str, rvalue="b''"),
|
| + 'str': partial(_extend_str, rvalue="''")})
|
| +else:
|
| + extend_builtins({'str': partial(_extend_str, rvalue="''"),
|
| + 'unicode': partial(_extend_str, rvalue="u''")})
|
| +
|
| +
|
| +def register_builtin_transform(transform, builtin_name):
|
| + """Register a new transform function for the given *builtin_name*.
|
| +
|
| + The transform function must accept two parameters, a node and
|
| + an optional context.
|
| + """
|
| + def _transform_wrapper(node, context=None):
|
| + result = transform(node, context=context)
|
| + if result:
|
| + result.parent = node
|
| + result.lineno = node.lineno
|
| + result.col_offset = node.col_offset
|
| + return iter([result])
|
| +
|
| + MANAGER.register_transform(nodes.CallFunc,
|
| + inference_tip(_transform_wrapper),
|
| + lambda n: (isinstance(n.func, nodes.Name) and
|
| + n.func.name == builtin_name))
|
| +
|
| +
|
| +def _generic_inference(node, context, node_type, transform):
|
| + args = node.args
|
| + if not args:
|
| + return node_type()
|
| + if len(node.args) > 1:
|
| + raise UseInferenceDefault()
|
| +
|
| + arg, = args
|
| + transformed = transform(arg)
|
| + if not transformed:
|
| + try:
|
| + infered = next(arg.infer(context=context))
|
| + except (InferenceError, StopIteration):
|
| + raise UseInferenceDefault()
|
| + if infered is YES:
|
| + raise UseInferenceDefault()
|
| + transformed = transform(infered)
|
| + if not transformed or transformed is YES:
|
| + raise UseInferenceDefault()
|
| + return transformed
|
| +
|
| +
|
| +def _generic_transform(arg, klass, iterables, build_elts):
|
| + if isinstance(arg, klass):
|
| + return arg
|
| + elif isinstance(arg, iterables):
|
| + if not all(isinstance(elt, nodes.Const)
|
| + for elt in arg.elts):
|
| + # TODO(cpopa): Don't support heterogenous elements.
|
| + # Not yet, though.
|
| + raise UseInferenceDefault()
|
| + elts = [elt.value for elt in arg.elts]
|
| + elif isinstance(arg, nodes.Dict):
|
| + if not all(isinstance(elt[0], nodes.Const)
|
| + for elt in arg.items):
|
| + raise UseInferenceDefault()
|
| + elts = [item[0].value for item in arg.items]
|
| + elif (isinstance(arg, nodes.Const) and
|
| + isinstance(arg.value, (six.string_types, six.binary_type))):
|
| + elts = arg.value
|
| + else:
|
| + return
|
| + return klass(elts=build_elts(elts))
|
| +
|
| +
|
| +def _infer_builtin(node, context,
|
| + klass=None, iterables=None,
|
| + build_elts=None):
|
| + transform_func = partial(
|
| + _generic_transform,
|
| + klass=klass,
|
| + iterables=iterables,
|
| + build_elts=build_elts)
|
| +
|
| + return _generic_inference(node, context, klass, transform_func)
|
| +
|
| +# pylint: disable=invalid-name
|
| +infer_tuple = partial(
|
| + _infer_builtin,
|
| + klass=nodes.Tuple,
|
| + iterables=(nodes.List, nodes.Set),
|
| + build_elts=tuple)
|
| +
|
| +infer_list = partial(
|
| + _infer_builtin,
|
| + klass=nodes.List,
|
| + iterables=(nodes.Tuple, nodes.Set),
|
| + build_elts=list)
|
| +
|
| +infer_set = partial(
|
| + _infer_builtin,
|
| + klass=nodes.Set,
|
| + iterables=(nodes.List, nodes.Tuple),
|
| + build_elts=set)
|
| +
|
| +
|
| +def _get_elts(arg, context):
|
| + is_iterable = lambda n: isinstance(n,
|
| + (nodes.List, nodes.Tuple, nodes.Set))
|
| + try:
|
| + infered = next(arg.infer(context))
|
| + except (InferenceError, UnresolvableName):
|
| + raise UseInferenceDefault()
|
| + if isinstance(infered, nodes.Dict):
|
| + items = infered.items
|
| + elif is_iterable(infered):
|
| + items = []
|
| + for elt in infered.elts:
|
| + # If an item is not a pair of two items,
|
| + # then fallback to the default inference.
|
| + # Also, take in consideration only hashable items,
|
| + # tuples and consts. We are choosing Names as well.
|
| + if not is_iterable(elt):
|
| + raise UseInferenceDefault()
|
| + if len(elt.elts) != 2:
|
| + raise UseInferenceDefault()
|
| + if not isinstance(elt.elts[0],
|
| + (nodes.Tuple, nodes.Const, nodes.Name)):
|
| + raise UseInferenceDefault()
|
| + items.append(tuple(elt.elts))
|
| + else:
|
| + raise UseInferenceDefault()
|
| + return items
|
| +
|
| +def infer_dict(node, context=None):
|
| + """Try to infer a dict call to a Dict node.
|
| +
|
| + The function treats the following cases:
|
| +
|
| + * dict()
|
| + * dict(mapping)
|
| + * dict(iterable)
|
| + * dict(iterable, **kwargs)
|
| + * dict(mapping, **kwargs)
|
| + * dict(**kwargs)
|
| +
|
| + If a case can't be infered, we'll fallback to default inference.
|
| + """
|
| + has_keywords = lambda args: all(isinstance(arg, nodes.Keyword)
|
| + for arg in args)
|
| + if not node.args and not node.kwargs:
|
| + # dict()
|
| + return nodes.Dict()
|
| + elif has_keywords(node.args) and node.args:
|
| + # dict(a=1, b=2, c=4)
|
| + items = [(nodes.Const(arg.arg), arg.value) for arg in node.args]
|
| + elif (len(node.args) >= 2 and
|
| + has_keywords(node.args[1:])):
|
| + # dict(some_iterable, b=2, c=4)
|
| + elts = _get_elts(node.args[0], context)
|
| + keys = [(nodes.Const(arg.arg), arg.value) for arg in node.args[1:]]
|
| + items = elts + keys
|
| + elif len(node.args) == 1:
|
| + items = _get_elts(node.args[0], context)
|
| + else:
|
| + raise UseInferenceDefault()
|
| +
|
| + empty = nodes.Dict()
|
| + empty.items = items
|
| + return empty
|
| +
|
| +# Builtins inference
|
| +register_builtin_transform(infer_tuple, 'tuple')
|
| +register_builtin_transform(infer_set, 'set')
|
| +register_builtin_transform(infer_list, 'list')
|
| +register_builtin_transform(infer_dict, 'dict')
|
|
|