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

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

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

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 

16import sys 

17from collections.abc import Generator, Iterator 

18from functools import cached_property 

19from typing import Literal, NoReturn 

20 

21from astroid import bases, util 

22from astroid.context import InferenceContext 

23from astroid.exceptions import ( 

24 AttributeInferenceError, 

25 InferenceError, 

26 MroError, 

27 SuperError, 

28) 

29from astroid.interpreter import objectmodel 

30from astroid.manager import AstroidManager 

31from astroid.nodes import node_classes, scoped_nodes 

32from astroid.typing import InferenceResult, SuccessfulInferenceResult 

33 

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

35 from typing import Self 

36else: 

37 from typing_extensions import Self 

38 

39 

40class FrozenSet(node_classes.BaseContainer): 

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

42 

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

44 return "builtins.frozenset" 

45 

46 def _infer(self, context: InferenceContext | None = None): 

47 yield self 

48 

49 @cached_property 

50 def _proxied(self): 

51 ast_builtins = AstroidManager().builtins_module 

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

53 

54 

55class Super(node_classes.NodeNG): 

56 """Proxy class over a super call. 

57 

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

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

60 

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

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

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

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

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

66 """ 

67 

68 special_attributes = objectmodel.SuperModel() 

69 

70 def __init__( 

71 self, 

72 mro_pointer: SuccessfulInferenceResult, 

73 mro_type: SuccessfulInferenceResult, 

74 self_class: scoped_nodes.ClassDef, 

75 scope: scoped_nodes.FunctionDef, 

76 call: node_classes.Call, 

77 ) -> None: 

78 self.type = mro_type 

79 self.mro_pointer = mro_pointer 

80 self._class_based = False 

81 self._self_class = self_class 

82 self._scope = scope 

83 super().__init__( 

84 parent=scope, 

85 lineno=scope.lineno, 

86 col_offset=scope.col_offset, 

87 end_lineno=scope.end_lineno, 

88 end_col_offset=scope.end_col_offset, 

89 ) 

90 

91 def _infer(self, context: InferenceContext | None = None): 

92 yield self 

93 

94 def super_mro(self): 

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

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

97 raise SuperError( 

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

99 "type, not {mro_pointer}.", 

100 super_=self, 

101 ) 

102 

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

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

105 self._class_based = True 

106 mro_type = self.type 

107 else: 

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

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

110 raise SuperError( 

111 "The second argument to super must be an " 

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

113 super_=self, 

114 ) 

115 

116 mro = mro_type.mro() 

117 if self.mro_pointer not in mro: 

118 raise SuperError( 

119 "The second argument to super must be an " 

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

121 super_=self, 

122 ) 

123 

124 index = mro.index(self.mro_pointer) 

125 return mro[index + 1 :] 

126 

127 @cached_property 

128 def _proxied(self): 

129 ast_builtins = AstroidManager().builtins_module 

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

131 

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

133 return "builtins.super" 

134 

135 def display_type(self) -> str: 

136 return "Super of" 

137 

138 @property 

139 def name(self): 

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

141 return self.mro_pointer.name 

142 

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

144 return "super" 

145 

146 def igetattr( # noqa: C901 

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

148 ) -> Iterator[InferenceResult]: 

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

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

151 # from the special attributes dict 

152 if name == "__class__": 

153 yield self.special_attributes.lookup(name) 

154 return 

155 

156 try: 

157 mro = self.super_mro() 

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

159 # leak out as is from this function. 

160 except SuperError as exc: 

161 raise AttributeInferenceError( 

162 ( 

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

164 "is invalid." 

165 ), 

166 target=self, 

167 attribute=name, 

168 context=context, 

169 super_=exc.super_, 

170 ) from exc 

171 except MroError as exc: 

172 raise AttributeInferenceError( 

173 ( 

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

175 "invalid MRO." 

176 ), 

177 target=self, 

178 attribute=name, 

179 context=context, 

180 mros=exc.mros, 

181 cls=exc.cls, 

182 ) from exc 

183 found = False 

184 for cls in mro: 

185 if name not in cls.locals: 

186 continue 

187 

188 found = True 

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

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

191 yield inferred 

192 continue 

193 

194 # We can obtain different descriptors from a super depending 

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

196 if inferred.type == "classmethod": 

197 # Pass original caller for classmethod too 

198 yield bases.BoundMethod(inferred, cls, original_caller=self.type) 

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

200 yield inferred 

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

202 yield inferred 

203 elif isinstance(inferred, Property): 

204 function = inferred.function 

205 try: 

206 yield from function.infer_call_result( 

207 caller=self, context=context 

208 ) 

209 except InferenceError: 

210 yield util.Uninferable 

211 elif bases._is_property(inferred): 

212 # TODO: support other descriptors as well. 

213 try: 

214 yield from inferred.infer_call_result(self, context) 

215 except InferenceError: 

216 yield util.Uninferable 

217 else: 

218 # Pass original caller (self.type) so infer_call_result can 

219 # correctly resolve Self return types to the actual caller type 

220 yield bases.BoundMethod(inferred, cls, original_caller=self.type) 

221 

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

223 # attribute we look it up in the special attributes 

224 if not found and name in self.special_attributes: 

225 special_attr = self.special_attributes.lookup(name) 

226 if not isinstance(special_attr, node_classes.Unknown): 

227 yield special_attr 

228 return 

229 

230 if not found: 

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

232 

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

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

235 

236 

237class ExceptionInstance(bases.Instance): 

238 """Class for instances of exceptions. 

239 

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

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

242 the case of .args. 

243 """ 

244 

245 @cached_property 

246 def special_attributes(self): 

247 qname = self.qname() 

248 instance = objectmodel.BUILTIN_EXCEPTIONS.get( 

249 qname, objectmodel.ExceptionInstanceModel 

250 ) 

251 return instance()(self) 

252 

253 

254class DictInstance(bases.Instance): 

255 """Special kind of instances for dictionaries. 

256 

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

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

259 """ 

260 

261 special_attributes = objectmodel.DictModel() 

262 

263 

264# Custom objects tailored for dictionary views 

265# returned by dict.items(), dict.keys() and dict.values(). 

266class DictItems(bases.Proxy): 

267 __str__ = node_classes.NodeNG.__str__ 

268 __repr__ = node_classes.NodeNG.__repr__ 

269 

270 

271class DictKeys(bases.Proxy): 

272 __str__ = node_classes.NodeNG.__str__ 

273 __repr__ = node_classes.NodeNG.__repr__ 

274 

275 

276class DictValues(bases.Proxy): 

277 __str__ = node_classes.NodeNG.__str__ 

278 __repr__ = node_classes.NodeNG.__repr__ 

279 

280 

281class PartialFunction(scoped_nodes.FunctionDef): 

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

283 

284 def __init__( 

285 self, 

286 call=None, 

287 name=None, 

288 lineno=None, 

289 col_offset=None, 

290 parent=None, 

291 *, 

292 end_lineno=None, 

293 end_col_offset=None, 

294 filled_args=None, 

295 filled_keywords=None, 

296 ): 

297 super().__init__( 

298 name, 

299 lineno=lineno, 

300 col_offset=col_offset, 

301 end_col_offset=end_col_offset, 

302 end_lineno=end_lineno, 

303 parent=parent, 

304 ) 

305 if call is None: 

306 self.filled_args = list(filled_args or []) 

307 self.filled_keywords = dict(filled_keywords or {}) 

308 else: 

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

310 self.filled_keywords = call.keyword_arguments 

311 

312 wrapped_function = call.positional_arguments[0] 

313 inferred_wrapped_function = next(wrapped_function.infer()) 

314 if isinstance(inferred_wrapped_function, PartialFunction): 

315 self.filled_args = ( 

316 inferred_wrapped_function.filled_args + self.filled_args 

317 ) 

318 self.filled_keywords = { 

319 **inferred_wrapped_function.filled_keywords, 

320 **self.filled_keywords, 

321 } 

322 

323 self.filled_positionals = len(self.filled_args) 

324 

325 def infer_call_result( 

326 self, 

327 caller: SuccessfulInferenceResult | None, 

328 context: InferenceContext | None = None, 

329 ) -> Iterator[InferenceResult]: 

330 if context: 

331 assert ( 

332 context.callcontext 

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

334 current_passed_keywords = { 

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

336 } 

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

338 if keyword not in current_passed_keywords: 

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

340 

341 call_context_args = context.callcontext.args or [] 

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

343 

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

345 

346 def qname(self) -> str: 

347 return self.__class__.__name__ 

348 

349 

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

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

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

353 

354 

355class Property(scoped_nodes.FunctionDef): 

356 """Class representing a Python property.""" 

357 

358 def __init__(self, function, name=None, lineno=None, col_offset=None, parent=None): 

359 self.function = function 

360 super().__init__( 

361 name, 

362 lineno=lineno, 

363 col_offset=col_offset, 

364 parent=parent, 

365 end_col_offset=function.end_col_offset, 

366 end_lineno=function.end_lineno, 

367 ) 

368 

369 special_attributes = objectmodel.PropertyModel() 

370 type = "property" 

371 

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

373 return "builtins.property" 

374 

375 def infer_call_result( 

376 self, 

377 caller: SuccessfulInferenceResult | None, 

378 context: InferenceContext | None = None, 

379 ) -> NoReturn: 

380 raise InferenceError("Properties are not callable") 

381 

382 def _infer(self, context: InferenceContext | None = None) -> Generator[Self]: 

383 yield self