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