summaryrefslogtreecommitdiff
path: root/astroid/interpreter/scope.py
blob: 7b49d49e167f70c4f5bcf28243af9dcc28ca0650 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# Copyright (c) 2015-2016 Cara Vinson <ceridwenv@gmail.com>
# Copyright (c) 2015-2016 Claudiu Popa <pcmanticore@gmail.com>

# 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

"""Implements logic for determing the scope of a node."""

import itertools

import six

from astroid.tree import treeabc
from astroid import util


# import pdb; pdb.set_trace()

@util.singledispatch
def _scope_by_parent(parent, node):
    """Detect the scope of the *node* by parent's rules.

    The scope for certain kind of nodes depends on the
    parent, as it is the case for default values of arguments
    and function annotation, where the scope is not the scope of
    the parent, but the parent scope of the parent.
    """
    # This is separated in multiple dispatch methods on parents,
    # in order to decouple the implementation for the normal cases.


def _node_arguments(node):
    for arg in itertools.chain(node.positional_and_keyword, node.keyword_only,
                               (node.vararg, ), (node.kwarg, )):
        if arg and arg.annotation:
            yield arg


@_scope_by_parent.register(treeabc.Arguments)
def _scope_by_argument_parent(parent, node):
    args = parent
    for param in itertools.chain(args.positional_and_keyword, args.keyword_only):
        if param.default == node:
            return args.parent.parent.scope()

    if six.PY3 and node in _node_arguments(args):
        return args.parent.parent.scope()


@_scope_by_parent.register(treeabc.FunctionDef)
def _scope_by_function_parent(parent, node):
    # Verify if the node is the return annotation of a function,
    # in which case the scope is the parent scope of the function.
    if six.PY3 and node is parent.returns:
        return parent.parent.scope()


@_scope_by_parent.register(treeabc.Comprehension)
def _scope_by_comprehension_parent(parent, node):
    # Get the scope of a node which has a comprehension
    # as a parent. The rules are a bit hairy, but in essence
    # it is simple enough: list comprehensions leaks variables
    # on Python 2, so they have the parent scope of the list comprehension
    # itself. The iter part of the comprehension has almost always
    # another scope than the comprehension itself, but only for the
    # first generator (the outer one). Other comprehensions don't leak
    # variables on Python 2 and 3.

    comprehension = parent_scope = parent.parent
    generators = comprehension.generators

    # The first outer generator always has a different scope
    first_iter = generators[0].iter
    if node is first_iter:
        return parent_scope.parent.scope()

    # This might not be correct for all the cases, but it
    # should be enough for most of them.
    if six.PY2 and isinstance(parent_scope, treeabc.ListComp):
        return parent_scope.parent.scope()
    return parent.scope()


@util.singledispatch
def node_scope(node):
    """Get the scope of the given node."""
    scope = _scope_by_parent(node.parent, node)
    return scope or node.parent.scope()


@node_scope.register(treeabc.Decorators)
def _decorators_scope(node):
    return node.parent.parent.scope()


@node_scope.register(treeabc.Module)
@node_scope.register(treeabc.GeneratorExp)
@node_scope.register(treeabc.DictComp)
@node_scope.register(treeabc.SetComp)
@node_scope.register(treeabc.Lambda)
@node_scope.register(treeabc.FunctionDef)
@node_scope.register(treeabc.ClassDef)
def _scoped_nodes(node):
    return node

if six.PY3:
    node_scope.register(treeabc.ListComp, _scoped_nodes)