Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/astroid/nodes/scoped_nodes/mixin.py: 88%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

72 statements  

1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html 

2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE 

3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt 

4 

5"""This module contains mixin classes for scoped nodes.""" 

6 

7from __future__ import annotations 

8 

9import sys 

10from typing import TYPE_CHECKING, overload 

11 

12from astroid.exceptions import ParentMissingError 

13from astroid.filter_statements import _filter_stmts 

14from astroid.nodes import _base_nodes, scoped_nodes 

15from astroid.nodes.scoped_nodes.utils import builtin_lookup 

16from astroid.typing import InferenceResult, SuccessfulInferenceResult 

17 

18if sys.version_info >= (3, 11): 

19 from typing import Self 

20else: 

21 from typing_extensions import Self 

22if TYPE_CHECKING: 

23 from astroid import nodes 

24 

25 

26class LocalsDictNodeNG(_base_nodes.LookupMixIn): 

27 """this class provides locals handling common to Module, FunctionDef 

28 and ClassDef nodes, including a dict like interface for direct access 

29 to locals information 

30 """ 

31 

32 # attributes below are set by the builder module or by raw factories 

33 locals: dict[str, list[InferenceResult]] 

34 """A map of the name of a local variable to the node defining the local.""" 

35 

36 def qname(self) -> str: 

37 """Get the 'qualified' name of the node. 

38 

39 For example: module.name, module.class.name ... 

40 

41 :returns: The qualified name. 

42 :rtype: str 

43 """ 

44 # pylint: disable=no-member; github.com/pylint-dev/astroid/issues/278 

45 if self.parent is None: 

46 return self.name 

47 try: 

48 return f"{self.parent.frame().qname()}.{self.name}" 

49 except ParentMissingError: 

50 return self.name 

51 

52 def scope(self) -> Self: 

53 """The first parent node defining a new scope. 

54 

55 :returns: The first parent scope node. 

56 :rtype: Module or FunctionDef or ClassDef or Lambda or GenExpr 

57 """ 

58 return self 

59 

60 def scope_lookup( 

61 self, node: _base_nodes.LookupMixIn, name: str, offset: int = 0 

62 ) -> tuple[LocalsDictNodeNG, list[nodes.NodeNG]]: 

63 """Lookup where the given variable is assigned. 

64 

65 :param node: The node to look for assignments up to. 

66 Any assignments after the given node are ignored. 

67 

68 :param name: The name of the variable to find assignments for. 

69 

70 :param offset: The line offset to filter statements up to. 

71 

72 :returns: This scope node and the list of assignments associated to the 

73 given name according to the scope where it has been found (locals, 

74 globals or builtin). 

75 """ 

76 raise NotImplementedError 

77 

78 def _scope_lookup( 

79 self, node: _base_nodes.LookupMixIn, name: str, offset: int = 0 

80 ) -> tuple[LocalsDictNodeNG, list[nodes.NodeNG]]: 

81 """XXX method for interfacing the scope lookup""" 

82 try: 

83 stmts = _filter_stmts(node, self.locals[name], self, offset) 

84 except KeyError: 

85 stmts = () 

86 if stmts: 

87 return self, stmts 

88 

89 # Handle nested scopes: since class names do not extend to nested 

90 # scopes (e.g., methods), we find the next enclosing non-class scope 

91 pscope = self.parent and self.parent.scope() 

92 while pscope is not None: 

93 if not isinstance(pscope, scoped_nodes.ClassDef): 

94 return pscope.scope_lookup(node, name) 

95 pscope = pscope.parent and pscope.parent.scope() 

96 

97 # self is at the top level of a module, or is enclosed only by ClassDefs 

98 return builtin_lookup(name) 

99 

100 def set_local(self, name: str, stmt: nodes.NodeNG) -> None: 

101 """Define that the given name is declared in the given statement node. 

102 

103 .. seealso:: :meth:`scope` 

104 

105 :param name: The name that is being defined. 

106 

107 :param stmt: The statement that defines the given name. 

108 """ 

109 # assert not stmt in self.locals.get(name, ()), (self, stmt) 

110 self.locals.setdefault(name, []).append(stmt) 

111 

112 __setitem__ = set_local 

113 

114 def _append_node(self, child: nodes.NodeNG) -> None: 

115 """append a child, linking it in the tree""" 

116 # pylint: disable=no-member; depending by the class 

117 # which uses the current class as a mixin or base class. 

118 # It's rewritten in 2.0, so it makes no sense for now 

119 # to spend development time on it. 

120 self.body.append(child) # type: ignore[attr-defined] 

121 child.parent = self 

122 

123 @overload 

124 def add_local_node( 

125 self, child_node: nodes.ClassDef, name: str | None = ... 

126 ) -> None: ... 

127 

128 @overload 

129 def add_local_node(self, child_node: nodes.NodeNG, name: str) -> None: ... 

130 

131 def add_local_node(self, child_node: nodes.NodeNG, name: str | None = None) -> None: 

132 """Append a child that should alter the locals of this scope node. 

133 

134 :param child_node: The child node that will alter locals. 

135 

136 :param name: The name of the local that will be altered by 

137 the given child node. 

138 """ 

139 if name != "__class__": 

140 # add __class__ node as a child will cause infinite recursion later! 

141 self._append_node(child_node) 

142 self.set_local(name or child_node.name, child_node) # type: ignore[attr-defined] 

143 

144 def __getitem__(self, item: str) -> SuccessfulInferenceResult: 

145 """The first node the defines the given local. 

146 

147 :param item: The name of the locally defined object. 

148 

149 :raises KeyError: If the name is not defined. 

150 """ 

151 return self.locals[item][0] 

152 

153 def __iter__(self): 

154 """Iterate over the names of locals defined in this scoped node. 

155 

156 :returns: The names of the defined locals. 

157 :rtype: iterable(str) 

158 """ 

159 return iter(self.keys()) 

160 

161 def keys(self): 

162 """The names of locals defined in this scoped node. 

163 

164 :returns: The names of the defined locals. 

165 :rtype: list(str) 

166 """ 

167 return list(self.locals.keys()) 

168 

169 def values(self): 

170 """The nodes that define the locals in this scoped node. 

171 

172 :returns: The nodes that define locals. 

173 :rtype: list(NodeNG) 

174 """ 

175 # pylint: disable=consider-using-dict-items 

176 # It look like this class override items/keys/values, 

177 # probably not worth the headache 

178 return [self[key] for key in self.keys()] 

179 

180 def items(self): 

181 """Get the names of the locals and the node that defines the local. 

182 

183 :returns: The names of locals and their associated node. 

184 :rtype: list(tuple(str, NodeNG)) 

185 """ 

186 return list(zip(self.keys(), self.values())) 

187 

188 def __contains__(self, name) -> bool: 

189 """Check if a local is defined in this scope. 

190 

191 :param name: The name of the local to check for. 

192 :type name: str 

193 

194 :returns: Whether this node has a local of the given name, 

195 """ 

196 return name in self.locals 

197 

198 

199class ComprehensionScope(LocalsDictNodeNG): 

200 """Scoping for different types of comprehensions.""" 

201 

202 scope_lookup = LocalsDictNodeNG._scope_lookup 

203 

204 generators: list[nodes.Comprehension] 

205 """The generators that are looped through."""