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') |