Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/astroid/helpers.py: 87%

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

155 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 helper utilities.""" 

6 

7from __future__ import annotations 

8 

9import warnings 

10from collections.abc import Generator 

11 

12from astroid import bases, manager, nodes, objects, raw_building, util 

13from astroid.context import CallContext, InferenceContext 

14from astroid.exceptions import ( 

15 AstroidTypeError, 

16 AttributeInferenceError, 

17 InferenceError, 

18 MroError, 

19 _NonDeducibleTypeHierarchy, 

20) 

21from astroid.nodes import scoped_nodes 

22from astroid.typing import InferenceResult 

23from astroid.util import safe_infer as real_safe_infer 

24 

25 

26def safe_infer( 

27 node: nodes.NodeNG | bases.Proxy | util.UninferableBase, 

28 context: InferenceContext | None = None, 

29) -> InferenceResult | None: 

30 # When removing, also remove the real_safe_infer alias 

31 warnings.warn( 

32 "Import safe_infer from astroid.util; this shim in astroid.helpers will be removed.", 

33 DeprecationWarning, 

34 stacklevel=2, 

35 ) 

36 return real_safe_infer(node, context=context) 

37 

38 

39def _build_proxy_class(cls_name: str, builtins: nodes.Module) -> nodes.ClassDef: 

40 proxy = raw_building.build_class(cls_name) 

41 proxy.parent = builtins 

42 return proxy 

43 

44 

45def _function_type( 

46 function: nodes.Lambda | nodes.FunctionDef | bases.UnboundMethod, 

47 builtins: nodes.Module, 

48) -> nodes.ClassDef: 

49 if isinstance(function, (scoped_nodes.Lambda, scoped_nodes.FunctionDef)): 

50 if function.root().name == "builtins": 

51 cls_name = "builtin_function_or_method" 

52 else: 

53 cls_name = "function" 

54 elif isinstance(function, bases.BoundMethod): 

55 cls_name = "method" 

56 else: 

57 cls_name = "function" 

58 return _build_proxy_class(cls_name, builtins) 

59 

60 

61def _object_type( 

62 node: InferenceResult, context: InferenceContext | None = None 

63) -> Generator[InferenceResult | None, None, None]: 

64 astroid_manager = manager.AstroidManager() 

65 builtins = astroid_manager.builtins_module 

66 context = context or InferenceContext() 

67 

68 for inferred in node.infer(context=context): 

69 if isinstance(inferred, scoped_nodes.ClassDef): 

70 if inferred.newstyle: 

71 metaclass = inferred.metaclass(context=context) 

72 if metaclass: 

73 yield metaclass 

74 continue 

75 yield builtins.getattr("type")[0] 

76 elif isinstance( 

77 inferred, 

78 (scoped_nodes.Lambda, bases.UnboundMethod, scoped_nodes.FunctionDef), 

79 ): 

80 yield _function_type(inferred, builtins) 

81 elif isinstance(inferred, scoped_nodes.Module): 

82 yield _build_proxy_class("module", builtins) 

83 elif isinstance(inferred, nodes.Unknown): 

84 raise InferenceError 

85 elif isinstance(inferred, util.UninferableBase): 

86 yield inferred 

87 elif isinstance(inferred, (bases.Proxy, nodes.Slice, objects.Super)): 

88 yield inferred._proxied 

89 else: # pragma: no cover 

90 raise AssertionError(f"We don't handle {type(inferred)} currently") 

91 

92 

93def object_type( 

94 node: InferenceResult, context: InferenceContext | None = None 

95) -> InferenceResult | None: 

96 """Obtain the type of the given node. 

97 

98 This is used to implement the ``type`` builtin, which means that it's 

99 used for inferring type calls, as well as used in a couple of other places 

100 in the inference. 

101 The node will be inferred first, so this function can support all 

102 sorts of objects, as long as they support inference. 

103 """ 

104 

105 try: 

106 types = set(_object_type(node, context)) 

107 except InferenceError: 

108 return util.Uninferable 

109 if len(types) > 1 or not types: 

110 return util.Uninferable 

111 return next(iter(types)) 

112 

113 

114def _object_type_is_subclass( 

115 obj_type: InferenceResult | None, 

116 class_or_seq: list[InferenceResult], 

117 context: InferenceContext | None = None, 

118) -> util.UninferableBase | bool: 

119 if isinstance(obj_type, util.UninferableBase) or not isinstance( 

120 obj_type, nodes.ClassDef 

121 ): 

122 return util.Uninferable 

123 

124 # Instances are not types 

125 class_seq = [ 

126 item if not isinstance(item, bases.Instance) else util.Uninferable 

127 for item in class_or_seq 

128 ] 

129 # strict compatibility with issubclass 

130 # issubclass(type, (object, 1)) evaluates to true 

131 # issubclass(object, (1, type)) raises TypeError 

132 for klass in class_seq: 

133 if isinstance(klass, util.UninferableBase): 

134 raise AstroidTypeError("arg 2 must be a type or tuple of types") 

135 

136 for obj_subclass in obj_type.mro(): 

137 if obj_subclass == klass: 

138 return True 

139 return False 

140 

141 

142def object_isinstance( 

143 node: InferenceResult, 

144 class_or_seq: list[InferenceResult], 

145 context: InferenceContext | None = None, 

146) -> util.UninferableBase | bool: 

147 """Check if a node 'isinstance' any node in class_or_seq. 

148 

149 :raises AstroidTypeError: if the given ``classes_or_seq`` are not types 

150 """ 

151 obj_type = object_type(node, context) 

152 if isinstance(obj_type, util.UninferableBase): 

153 return util.Uninferable 

154 return _object_type_is_subclass(obj_type, class_or_seq, context=context) 

155 

156 

157def object_issubclass( 

158 node: nodes.NodeNG, 

159 class_or_seq: list[InferenceResult], 

160 context: InferenceContext | None = None, 

161) -> util.UninferableBase | bool: 

162 """Check if a type is a subclass of any node in class_or_seq. 

163 

164 :raises AstroidTypeError: if the given ``classes_or_seq`` are not types 

165 :raises AstroidError: if the type of the given node cannot be inferred 

166 or its type's mro doesn't work 

167 """ 

168 if not isinstance(node, nodes.ClassDef): 

169 raise TypeError(f"{node} needs to be a ClassDef node") 

170 return _object_type_is_subclass(node, class_or_seq, context=context) 

171 

172 

173def has_known_bases(klass, context: InferenceContext | None = None) -> bool: 

174 """Return whether all base classes of a class could be inferred.""" 

175 try: 

176 return klass._all_bases_known 

177 except AttributeError: 

178 pass 

179 for base in klass.bases: 

180 result = real_safe_infer(base, context=context) 

181 # TODO: check for A->B->A->B pattern in class structure too? 

182 if ( 

183 not isinstance(result, scoped_nodes.ClassDef) 

184 or result is klass 

185 or not has_known_bases(result, context=context) 

186 ): 

187 klass._all_bases_known = False 

188 return False 

189 klass._all_bases_known = True 

190 return True 

191 

192 

193def _type_check(type1, type2) -> bool: 

194 if not all(map(has_known_bases, (type1, type2))): 

195 raise _NonDeducibleTypeHierarchy 

196 

197 if not all([type1.newstyle, type2.newstyle]): 

198 return False 

199 try: 

200 return type1 in type2.mro()[:-1] 

201 except MroError as e: 

202 # The MRO is invalid. 

203 raise _NonDeducibleTypeHierarchy from e 

204 

205 

206def is_subtype(type1, type2) -> bool: 

207 """Check if *type1* is a subtype of *type2*.""" 

208 return _type_check(type1=type2, type2=type1) 

209 

210 

211def is_supertype(type1, type2) -> bool: 

212 """Check if *type2* is a supertype of *type1*.""" 

213 return _type_check(type1, type2) 

214 

215 

216def class_instance_as_index(node: bases.Instance) -> nodes.Const | None: 

217 """Get the value as an index for the given instance. 

218 

219 If an instance provides an __index__ method, then it can 

220 be used in some scenarios where an integer is expected, 

221 for instance when multiplying or subscripting a list. 

222 """ 

223 context = InferenceContext() 

224 try: 

225 for inferred in node.igetattr("__index__", context=context): 

226 if not isinstance(inferred, bases.BoundMethod): 

227 continue 

228 

229 context.boundnode = node 

230 context.callcontext = CallContext(args=[], callee=inferred) 

231 for result in inferred.infer_call_result(node, context=context): 

232 if isinstance(result, nodes.Const) and isinstance(result.value, int): 

233 return result 

234 except InferenceError: 

235 pass 

236 return None 

237 

238 

239def object_len(node, context: InferenceContext | None = None): 

240 """Infer length of given node object. 

241 

242 :param Union[nodes.ClassDef, nodes.Instance] node: 

243 :param node: Node to infer length of 

244 

245 :raises AstroidTypeError: If an invalid node is returned 

246 from __len__ method or no __len__ method exists 

247 :raises InferenceError: If the given node cannot be inferred 

248 or if multiple nodes are inferred or if the code executed in python 

249 would result in a infinite recursive check for length 

250 :rtype int: Integer length of node 

251 """ 

252 # pylint: disable=import-outside-toplevel; circular import 

253 from astroid.objects import FrozenSet 

254 

255 inferred_node = real_safe_infer(node, context=context) 

256 

257 # prevent self referential length calls from causing a recursion error 

258 # see https://github.com/pylint-dev/astroid/issues/777 

259 node_frame = node.frame() 

260 if ( 

261 isinstance(node_frame, scoped_nodes.FunctionDef) 

262 and node_frame.name == "__len__" 

263 and isinstance(inferred_node, bases.Proxy) 

264 and inferred_node._proxied == node_frame.parent 

265 ): 

266 message = ( 

267 "Self referential __len__ function will " 

268 "cause a RecursionError on line {} of {}".format( 

269 node.lineno, node.root().file 

270 ) 

271 ) 

272 raise InferenceError(message) 

273 

274 if inferred_node is None or isinstance(inferred_node, util.UninferableBase): 

275 raise InferenceError(node=node) 

276 if isinstance(inferred_node, nodes.Const) and isinstance( 

277 inferred_node.value, (bytes, str) 

278 ): 

279 return len(inferred_node.value) 

280 if isinstance(inferred_node, (nodes.List, nodes.Set, nodes.Tuple, FrozenSet)): 

281 return len(inferred_node.elts) 

282 if isinstance(inferred_node, nodes.Dict): 

283 return len(inferred_node.items) 

284 

285 node_type = object_type(inferred_node, context=context) 

286 if not node_type: 

287 raise InferenceError(node=node) 

288 

289 try: 

290 len_call = next(node_type.igetattr("__len__", context=context)) 

291 except StopIteration as e: 

292 raise AstroidTypeError(str(e)) from e 

293 except AttributeInferenceError as e: 

294 raise AstroidTypeError( 

295 f"object of type '{node_type.pytype()}' has no len()" 

296 ) from e 

297 

298 inferred = len_call.infer_call_result(node, context) 

299 if isinstance(inferred, util.UninferableBase): 

300 raise InferenceError(node=node, context=context) 

301 result_of_len = next(inferred, None) 

302 if ( 

303 isinstance(result_of_len, nodes.Const) 

304 and result_of_len.pytype() == "builtins.int" 

305 ): 

306 return result_of_len.value 

307 if ( 

308 result_of_len is None 

309 or isinstance(result_of_len, bases.Instance) 

310 and result_of_len.is_subtype_of("builtins.int") 

311 ): 

312 # Fake a result as we don't know the arguments of the instance call. 

313 return 0 

314 raise AstroidTypeError( 

315 f"'{result_of_len}' object cannot be interpreted as an integer" 

316 ) 

317 

318 

319def _higher_function_scope(node: nodes.NodeNG) -> nodes.FunctionDef | None: 

320 """Search for the first function which encloses the given 

321 scope. 

322 

323 This can be used for looking up in that function's 

324 scope, in case looking up in a lower scope for a particular 

325 name fails. 

326 

327 :param node: A scope node. 

328 :returns: 

329 ``None``, if no parent function scope was found, 

330 otherwise an instance of :class:`astroid.nodes.scoped_nodes.Function`, 

331 which encloses the given node. 

332 """ 

333 current = node 

334 while current.parent and not isinstance(current.parent, nodes.FunctionDef): 

335 current = current.parent 

336 if current and current.parent: 

337 return current.parent 

338 return None