Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/astroid/context.py: 64%

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

83 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"""Various context related utilities, including inference and call contexts.""" 

6 

7from __future__ import annotations 

8 

9import contextlib 

10import pprint 

11from collections.abc import Iterator, Sequence 

12from typing import TYPE_CHECKING, Optional 

13 

14from astroid.typing import InferenceResult, SuccessfulInferenceResult 

15 

16if TYPE_CHECKING: 

17 from astroid import constraint, nodes 

18 from astroid.nodes.node_classes import Keyword 

19 from astroid.nodes.node_ng import NodeNG 

20 

21_InferenceCache = dict[ 

22 tuple["NodeNG", Optional[str], Optional[str], Optional[str]], Sequence["NodeNG"] 

23] 

24 

25_INFERENCE_CACHE: _InferenceCache = {} 

26 

27 

28def _invalidate_cache() -> None: 

29 _INFERENCE_CACHE.clear() 

30 

31 

32class InferenceContext: 

33 """Provide context for inference. 

34 

35 Store already inferred nodes to save time 

36 Account for already visited nodes to stop infinite recursion 

37 """ 

38 

39 __slots__ = ( 

40 "_nodes_inferred", 

41 "boundnode", 

42 "callcontext", 

43 "constraints", 

44 "extra_context", 

45 "lookupname", 

46 "path", 

47 ) 

48 

49 max_inferred = 100 

50 

51 def __init__( 

52 self, 

53 path: set[tuple[nodes.NodeNG, str | None]] | None = None, 

54 nodes_inferred: list[int] | None = None, 

55 ) -> None: 

56 if nodes_inferred is None: 

57 self._nodes_inferred = [0] 

58 else: 

59 self._nodes_inferred = nodes_inferred 

60 

61 self.path = path or set() 

62 """Path of visited nodes and their lookupname. 

63 

64 Currently this key is ``(node, context.lookupname)`` 

65 """ 

66 self.lookupname: str | None = None 

67 """The original name of the node. 

68 

69 e.g. 

70 foo = 1 

71 The inference of 'foo' is nodes.Const(1) but the lookup name is 'foo' 

72 """ 

73 self.callcontext: CallContext | None = None 

74 """The call arguments and keywords for the given context.""" 

75 self.boundnode: SuccessfulInferenceResult | None = None 

76 """The bound node of the given context. 

77 

78 e.g. the bound node of object.__new__(cls) is the object node 

79 """ 

80 self.extra_context: dict[SuccessfulInferenceResult, InferenceContext] = {} 

81 """Context that needs to be passed down through call stacks for call arguments.""" 

82 

83 self.constraints: dict[str, dict[nodes.If, set[constraint.Constraint]]] = {} 

84 """The constraints on nodes.""" 

85 

86 @property 

87 def nodes_inferred(self) -> int: 

88 """ 

89 Number of nodes inferred in this context and all its clones/descendents. 

90 

91 Wrap inner value in a mutable cell to allow for mutating a class 

92 variable in the presence of __slots__ 

93 """ 

94 return self._nodes_inferred[0] 

95 

96 @nodes_inferred.setter 

97 def nodes_inferred(self, value: int) -> None: 

98 self._nodes_inferred[0] = value 

99 

100 @property 

101 def inferred(self) -> _InferenceCache: 

102 """ 

103 Inferred node contexts to their mapped results. 

104 

105 Currently the key is ``(node, lookupname, callcontext, boundnode)`` 

106 and the value is tuple of the inferred results 

107 """ 

108 return _INFERENCE_CACHE 

109 

110 def push(self, node: nodes.NodeNG) -> bool: 

111 """Push node into inference path. 

112 

113 Allows one to see if the given node has already 

114 been looked at for this inference context 

115 """ 

116 name = self.lookupname 

117 if (node, name) in self.path: 

118 return True 

119 

120 self.path.add((node, name)) 

121 return False 

122 

123 def clone(self) -> InferenceContext: 

124 """Clone inference path. 

125 

126 For example, each side of a binary operation (BinOp) 

127 starts with the same context but diverge as each side is inferred 

128 so the InferenceContext will need be cloned 

129 """ 

130 # XXX copy lookupname/callcontext ? 

131 clone = InferenceContext(self.path.copy(), nodes_inferred=self._nodes_inferred) 

132 clone.callcontext = self.callcontext 

133 clone.boundnode = self.boundnode 

134 clone.extra_context = self.extra_context 

135 clone.constraints = self.constraints.copy() 

136 return clone 

137 

138 @contextlib.contextmanager 

139 def restore_path(self) -> Iterator[None]: 

140 path = set(self.path) 

141 yield 

142 self.path = path 

143 

144 def is_empty(self) -> bool: 

145 return ( 

146 not self.path 

147 and not self.nodes_inferred 

148 and not self.callcontext 

149 and not self.boundnode 

150 and not self.lookupname 

151 and not self.callcontext 

152 and not self.extra_context 

153 and not self.constraints 

154 ) 

155 

156 def __str__(self) -> str: 

157 state = ( 

158 f"{field}={pprint.pformat(getattr(self, field), width=80 - len(field))}" 

159 for field in self.__slots__ 

160 ) 

161 return "{}({})".format(type(self).__name__, ",\n ".join(state)) 

162 

163 

164class CallContext: 

165 """Holds information for a call site.""" 

166 

167 __slots__ = ("args", "callee", "keywords") 

168 

169 def __init__( 

170 self, 

171 args: list[NodeNG], 

172 keywords: list[Keyword] | None = None, 

173 callee: InferenceResult | None = None, 

174 ): 

175 self.args = args # Call positional arguments 

176 if keywords: 

177 arg_value_pairs = [(arg.arg, arg.value) for arg in keywords] 

178 else: 

179 arg_value_pairs = [] 

180 self.keywords = arg_value_pairs # Call keyword arguments 

181 self.callee = callee # Function being called 

182 

183 

184def copy_context(context: InferenceContext | None) -> InferenceContext: 

185 """Clone a context if given, or return a fresh context.""" 

186 if context is not None: 

187 return context.clone() 

188 

189 return InferenceContext() 

190 

191 

192def bind_context_to_node( 

193 context: InferenceContext | None, node: SuccessfulInferenceResult 

194) -> InferenceContext: 

195 """Give a context a boundnode 

196 to retrieve the correct function name or attribute value 

197 with from further inference. 

198 

199 Do not use an existing context since the boundnode could then 

200 be incorrectly propagated higher up in the call stack. 

201 """ 

202 context = copy_context(context) 

203 context.boundnode = node 

204 return context