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

164 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:53 +0000

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

6Inference objects are a way to represent composite AST nodes, 

7which are used only as inference results, so they can't be found in the 

8original AST tree. For instance, inferring the following frozenset use, 

9leads to an inferred FrozenSet: 

10 

11 Call(func=Name('frozenset'), args=Tuple(...)) 

12""" 

13 

14from __future__ import annotations 

15 

16from collections.abc import Generator, Iterator 

17from functools import cached_property 

18from typing import Any, Literal, NoReturn, TypeVar 

19 

20from astroid import bases, decorators, util 

21from astroid.context import InferenceContext 

22from astroid.exceptions import ( 

23 AttributeInferenceError, 

24 InferenceError, 

25 MroError, 

26 SuperError, 

27) 

28from astroid.interpreter import objectmodel 

29from astroid.manager import AstroidManager 

30from astroid.nodes import node_classes, scoped_nodes 

31from astroid.typing import InferenceResult, SuccessfulInferenceResult 

32 

33_T = TypeVar("_T") 

34 

35 

36class FrozenSet(node_classes.BaseContainer): 

37 """Class representing a FrozenSet composite node.""" 

38 

39 def pytype(self) -> Literal["builtins.frozenset"]: 

40 return "builtins.frozenset" 

41 

42 def _infer(self, context: InferenceContext | None = None, **kwargs: Any): 

43 yield self 

44 

45 @cached_property 

46 def _proxied(self): # pylint: disable=method-hidden 

47 ast_builtins = AstroidManager().builtins_module 

48 return ast_builtins.getattr("frozenset")[0] 

49 

50 

51class Super(node_classes.NodeNG): 

52 """Proxy class over a super call. 

53 

54 This class offers almost the same behaviour as Python's super, 

55 which is MRO lookups for retrieving attributes from the parents. 

56 

57 The *mro_pointer* is the place in the MRO from where we should 

58 start looking, not counting it. *mro_type* is the object which 

59 provides the MRO, it can be both a type or an instance. 

60 *self_class* is the class where the super call is, while 

61 *scope* is the function where the super call is. 

62 """ 

63 

64 special_attributes = objectmodel.SuperModel() 

65 

66 def __init__( 

67 self, 

68 mro_pointer: SuccessfulInferenceResult, 

69 mro_type: SuccessfulInferenceResult, 

70 self_class: scoped_nodes.ClassDef, 

71 scope: scoped_nodes.FunctionDef, 

72 call: node_classes.Call, 

73 ) -> None: 

74 self.type = mro_type 

75 self.mro_pointer = mro_pointer 

76 self._class_based = False 

77 self._self_class = self_class 

78 self._scope = scope 

79 super().__init__( 

80 parent=scope, 

81 lineno=scope.lineno, 

82 col_offset=scope.col_offset, 

83 end_lineno=scope.end_lineno, 

84 end_col_offset=scope.end_col_offset, 

85 ) 

86 

87 def _infer(self, context: InferenceContext | None = None, **kwargs: Any): 

88 yield self 

89 

90 def super_mro(self): 

91 """Get the MRO which will be used to lookup attributes in this super.""" 

92 if not isinstance(self.mro_pointer, scoped_nodes.ClassDef): 

93 raise SuperError( 

94 "The first argument to super must be a subtype of " 

95 "type, not {mro_pointer}.", 

96 super_=self, 

97 ) 

98 

99 if isinstance(self.type, scoped_nodes.ClassDef): 

100 # `super(type, type)`, most likely in a class method. 

101 self._class_based = True 

102 mro_type = self.type 

103 else: 

104 mro_type = getattr(self.type, "_proxied", None) 

105 if not isinstance(mro_type, (bases.Instance, scoped_nodes.ClassDef)): 

106 raise SuperError( 

107 "The second argument to super must be an " 

108 "instance or subtype of type, not {type}.", 

109 super_=self, 

110 ) 

111 

112 if not mro_type.newstyle: 

113 raise SuperError("Unable to call super on old-style classes.", super_=self) 

114 

115 mro = mro_type.mro() 

116 if self.mro_pointer not in mro: 

117 raise SuperError( 

118 "The second argument to super must be an " 

119 "instance or subtype of type, not {type}.", 

120 super_=self, 

121 ) 

122 

123 index = mro.index(self.mro_pointer) 

124 return mro[index + 1 :] 

125 

126 @cached_property 

127 def _proxied(self): 

128 ast_builtins = AstroidManager().builtins_module 

129 return ast_builtins.getattr("super")[0] 

130 

131 def pytype(self) -> Literal["builtins.super"]: 

132 return "builtins.super" 

133 

134 def display_type(self) -> str: 

135 return "Super of" 

136 

137 @property 

138 def name(self): 

139 """Get the name of the MRO pointer.""" 

140 return self.mro_pointer.name 

141 

142 def qname(self) -> Literal["super"]: 

143 return "super" 

144 

145 def igetattr( # noqa: C901 

146 self, name: str, context: InferenceContext | None = None 

147 ) -> Iterator[InferenceResult]: 

148 """Retrieve the inferred values of the given attribute name.""" 

149 # '__class__' is a special attribute that should be taken directly 

150 # from the special attributes dict 

151 if name == "__class__": 

152 yield self.special_attributes.lookup(name) 

153 return 

154 

155 try: 

156 mro = self.super_mro() 

157 # Don't let invalid MROs or invalid super calls 

158 # leak out as is from this function. 

159 except SuperError as exc: 

160 raise AttributeInferenceError( 

161 ( 

162 "Lookup for {name} on {target!r} because super call {super!r} " 

163 "is invalid." 

164 ), 

165 target=self, 

166 attribute=name, 

167 context=context, 

168 super_=exc.super_, 

169 ) from exc 

170 except MroError as exc: 

171 raise AttributeInferenceError( 

172 ( 

173 "Lookup for {name} on {target!r} failed because {cls!r} has an " 

174 "invalid MRO." 

175 ), 

176 target=self, 

177 attribute=name, 

178 context=context, 

179 mros=exc.mros, 

180 cls=exc.cls, 

181 ) from exc 

182 found = False 

183 for cls in mro: 

184 if name not in cls.locals: 

185 continue 

186 

187 found = True 

188 for inferred in bases._infer_stmts([cls[name]], context, frame=self): 

189 if not isinstance(inferred, scoped_nodes.FunctionDef): 

190 yield inferred 

191 continue 

192 

193 # We can obtain different descriptors from a super depending 

194 # on what we are accessing and where the super call is. 

195 if inferred.type == "classmethod": 

196 yield bases.BoundMethod(inferred, cls) 

197 elif self._scope.type == "classmethod" and inferred.type == "method": 

198 yield inferred 

199 elif self._class_based or inferred.type == "staticmethod": 

200 yield inferred 

201 elif isinstance(inferred, Property): 

202 function = inferred.function 

203 try: 

204 yield from function.infer_call_result( 

205 caller=self, context=context 

206 ) 

207 except InferenceError: 

208 yield util.Uninferable 

209 elif bases._is_property(inferred): 

210 # TODO: support other descriptors as well. 

211 try: 

212 yield from inferred.infer_call_result(self, context) 

213 except InferenceError: 

214 yield util.Uninferable 

215 else: 

216 yield bases.BoundMethod(inferred, cls) 

217 

218 # Only if we haven't found any explicit overwrites for the 

219 # attribute we look it up in the special attributes 

220 if not found and name in self.special_attributes: 

221 yield self.special_attributes.lookup(name) 

222 return 

223 

224 if not found: 

225 raise AttributeInferenceError(target=self, attribute=name, context=context) 

226 

227 def getattr(self, name, context: InferenceContext | None = None): 

228 return list(self.igetattr(name, context=context)) 

229 

230 

231class ExceptionInstance(bases.Instance): 

232 """Class for instances of exceptions. 

233 

234 It has special treatment for some of the exceptions's attributes, 

235 which are transformed at runtime into certain concrete objects, such as 

236 the case of .args. 

237 """ 

238 

239 @cached_property 

240 def special_attributes(self): 

241 qname = self.qname() 

242 instance = objectmodel.BUILTIN_EXCEPTIONS.get( 

243 qname, objectmodel.ExceptionInstanceModel 

244 ) 

245 return instance()(self) 

246 

247 

248class DictInstance(bases.Instance): 

249 """Special kind of instances for dictionaries. 

250 

251 This instance knows the underlying object model of the dictionaries, which means 

252 that methods such as .values or .items can be properly inferred. 

253 """ 

254 

255 special_attributes = objectmodel.DictModel() 

256 

257 

258# Custom objects tailored for dictionaries, which are used to 

259# disambiguate between the types of Python 2 dict's method returns 

260# and Python 3 (where they return set like objects). 

261class DictItems(bases.Proxy): 

262 __str__ = node_classes.NodeNG.__str__ 

263 __repr__ = node_classes.NodeNG.__repr__ 

264 

265 

266class DictKeys(bases.Proxy): 

267 __str__ = node_classes.NodeNG.__str__ 

268 __repr__ = node_classes.NodeNG.__repr__ 

269 

270 

271class DictValues(bases.Proxy): 

272 __str__ = node_classes.NodeNG.__str__ 

273 __repr__ = node_classes.NodeNG.__repr__ 

274 

275 

276class PartialFunction(scoped_nodes.FunctionDef): 

277 """A class representing partial function obtained via functools.partial.""" 

278 

279 @decorators.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead") 

280 def __init__( 

281 self, call, name=None, doc=None, lineno=None, col_offset=None, parent=None 

282 ): 

283 # TODO: Pass end_lineno, end_col_offset and parent as well 

284 super().__init__( 

285 name, 

286 lineno=lineno, 

287 col_offset=col_offset, 

288 parent=node_classes.Unknown(), 

289 end_col_offset=0, 

290 end_lineno=0, 

291 ) 

292 # Assigned directly to prevent triggering the DeprecationWarning. 

293 self._doc = doc 

294 # A typical FunctionDef automatically adds its name to the parent scope, 

295 # but a partial should not, so defer setting parent until after init 

296 self.parent = parent 

297 self.filled_args = call.positional_arguments[1:] 

298 self.filled_keywords = call.keyword_arguments 

299 

300 wrapped_function = call.positional_arguments[0] 

301 inferred_wrapped_function = next(wrapped_function.infer()) 

302 if isinstance(inferred_wrapped_function, PartialFunction): 

303 self.filled_args = inferred_wrapped_function.filled_args + self.filled_args 

304 self.filled_keywords = { 

305 **inferred_wrapped_function.filled_keywords, 

306 **self.filled_keywords, 

307 } 

308 

309 self.filled_positionals = len(self.filled_args) 

310 

311 def infer_call_result( 

312 self, 

313 caller: SuccessfulInferenceResult | None, 

314 context: InferenceContext | None = None, 

315 ) -> Iterator[InferenceResult]: 

316 if context: 

317 assert ( 

318 context.callcontext 

319 ), "CallContext should be set before inferring call result" 

320 current_passed_keywords = { 

321 keyword for (keyword, _) in context.callcontext.keywords 

322 } 

323 for keyword, value in self.filled_keywords.items(): 

324 if keyword not in current_passed_keywords: 

325 context.callcontext.keywords.append((keyword, value)) 

326 

327 call_context_args = context.callcontext.args or [] 

328 context.callcontext.args = self.filled_args + call_context_args 

329 

330 return super().infer_call_result(caller=caller, context=context) 

331 

332 def qname(self) -> str: 

333 return self.__class__.__name__ 

334 

335 

336# TODO: Hack to solve the circular import problem between node_classes and objects 

337# This is not needed in 2.0, which has a cleaner design overall 

338node_classes.Dict.__bases__ = (node_classes.NodeNG, DictInstance) 

339 

340 

341class Property(scoped_nodes.FunctionDef): 

342 """Class representing a Python property.""" 

343 

344 @decorators.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead") 

345 def __init__( 

346 self, function, name=None, doc=None, lineno=None, col_offset=None, parent=None 

347 ): 

348 self.function = function 

349 super().__init__( 

350 name, 

351 lineno=lineno, 

352 col_offset=col_offset, 

353 parent=parent, 

354 end_col_offset=function.end_col_offset, 

355 end_lineno=function.end_lineno, 

356 ) 

357 # Assigned directly to prevent triggering the DeprecationWarning. 

358 self._doc = doc 

359 

360 special_attributes = objectmodel.PropertyModel() 

361 type = "property" 

362 

363 def pytype(self) -> Literal["builtins.property"]: 

364 return "builtins.property" 

365 

366 def infer_call_result( 

367 self, 

368 caller: SuccessfulInferenceResult | None, 

369 context: InferenceContext | None = None, 

370 ) -> NoReturn: 

371 raise InferenceError("Properties are not callable") 

372 

373 def _infer( 

374 self: _T, context: InferenceContext | None = None, **kwargs: Any 

375 ) -> Generator[_T, None, None]: 

376 yield self