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

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

158 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 

16from collections.abc import Generator, Iterator 

17from functools import cached_property 

18from typing import Any, Literal, NoReturn, TypeVar 

19 

20from astroid import bases, 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 mro = mro_type.mro() 

113 if self.mro_pointer not in mro: 

114 raise SuperError( 

115 "The second argument to super must be an " 

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

117 super_=self, 

118 ) 

119 

120 index = mro.index(self.mro_pointer) 

121 return mro[index + 1 :] 

122 

123 @cached_property 

124 def _proxied(self): 

125 ast_builtins = AstroidManager().builtins_module 

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

127 

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

129 return "builtins.super" 

130 

131 def display_type(self) -> str: 

132 return "Super of" 

133 

134 @property 

135 def name(self): 

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

137 return self.mro_pointer.name 

138 

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

140 return "super" 

141 

142 def igetattr( # noqa: C901 

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

144 ) -> Iterator[InferenceResult]: 

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

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

147 # from the special attributes dict 

148 if name == "__class__": 

149 yield self.special_attributes.lookup(name) 

150 return 

151 

152 try: 

153 mro = self.super_mro() 

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

155 # leak out as is from this function. 

156 except SuperError as exc: 

157 raise AttributeInferenceError( 

158 ( 

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

160 "is invalid." 

161 ), 

162 target=self, 

163 attribute=name, 

164 context=context, 

165 super_=exc.super_, 

166 ) from exc 

167 except MroError as exc: 

168 raise AttributeInferenceError( 

169 ( 

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

171 "invalid MRO." 

172 ), 

173 target=self, 

174 attribute=name, 

175 context=context, 

176 mros=exc.mros, 

177 cls=exc.cls, 

178 ) from exc 

179 found = False 

180 for cls in mro: 

181 if name not in cls.locals: 

182 continue 

183 

184 found = True 

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

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

187 yield inferred 

188 continue 

189 

190 # We can obtain different descriptors from a super depending 

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

192 if inferred.type == "classmethod": 

193 yield bases.BoundMethod(inferred, cls) 

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

195 yield inferred 

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

197 yield inferred 

198 elif isinstance(inferred, Property): 

199 function = inferred.function 

200 try: 

201 yield from function.infer_call_result( 

202 caller=self, context=context 

203 ) 

204 except InferenceError: 

205 yield util.Uninferable 

206 elif bases._is_property(inferred): 

207 # TODO: support other descriptors as well. 

208 try: 

209 yield from inferred.infer_call_result(self, context) 

210 except InferenceError: 

211 yield util.Uninferable 

212 else: 

213 yield bases.BoundMethod(inferred, cls) 

214 

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

216 # attribute we look it up in the special attributes 

217 if not found and name in self.special_attributes: 

218 yield self.special_attributes.lookup(name) 

219 return 

220 

221 if not found: 

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

223 

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

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

226 

227 

228class ExceptionInstance(bases.Instance): 

229 """Class for instances of exceptions. 

230 

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

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

233 the case of .args. 

234 """ 

235 

236 @cached_property 

237 def special_attributes(self): 

238 qname = self.qname() 

239 instance = objectmodel.BUILTIN_EXCEPTIONS.get( 

240 qname, objectmodel.ExceptionInstanceModel 

241 ) 

242 return instance()(self) 

243 

244 

245class DictInstance(bases.Instance): 

246 """Special kind of instances for dictionaries. 

247 

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

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

250 """ 

251 

252 special_attributes = objectmodel.DictModel() 

253 

254 

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

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

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

258class DictItems(bases.Proxy): 

259 __str__ = node_classes.NodeNG.__str__ 

260 __repr__ = node_classes.NodeNG.__repr__ 

261 

262 

263class DictKeys(bases.Proxy): 

264 __str__ = node_classes.NodeNG.__str__ 

265 __repr__ = node_classes.NodeNG.__repr__ 

266 

267 

268class DictValues(bases.Proxy): 

269 __str__ = node_classes.NodeNG.__str__ 

270 __repr__ = node_classes.NodeNG.__repr__ 

271 

272 

273class PartialFunction(scoped_nodes.FunctionDef): 

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

275 

276 def __init__(self, call, name=None, lineno=None, col_offset=None, parent=None): 

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

278 super().__init__( 

279 name, 

280 lineno=lineno, 

281 col_offset=col_offset, 

282 parent=scoped_nodes.SYNTHETIC_ROOT, 

283 end_col_offset=0, 

284 end_lineno=0, 

285 ) 

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

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

288 self.parent = parent 

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

290 self.filled_keywords = call.keyword_arguments 

291 

292 wrapped_function = call.positional_arguments[0] 

293 inferred_wrapped_function = next(wrapped_function.infer()) 

294 if isinstance(inferred_wrapped_function, PartialFunction): 

295 self.filled_args = inferred_wrapped_function.filled_args + self.filled_args 

296 self.filled_keywords = { 

297 **inferred_wrapped_function.filled_keywords, 

298 **self.filled_keywords, 

299 } 

300 

301 self.filled_positionals = len(self.filled_args) 

302 

303 def infer_call_result( 

304 self, 

305 caller: SuccessfulInferenceResult | None, 

306 context: InferenceContext | None = None, 

307 ) -> Iterator[InferenceResult]: 

308 if context: 

309 assert ( 

310 context.callcontext 

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

312 current_passed_keywords = { 

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

314 } 

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

316 if keyword not in current_passed_keywords: 

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

318 

319 call_context_args = context.callcontext.args or [] 

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

321 

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

323 

324 def qname(self) -> str: 

325 return self.__class__.__name__ 

326 

327 

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

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

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

331 

332 

333class Property(scoped_nodes.FunctionDef): 

334 """Class representing a Python property.""" 

335 

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

337 self.function = function 

338 super().__init__( 

339 name, 

340 lineno=lineno, 

341 col_offset=col_offset, 

342 parent=parent, 

343 end_col_offset=function.end_col_offset, 

344 end_lineno=function.end_lineno, 

345 ) 

346 

347 special_attributes = objectmodel.PropertyModel() 

348 type = "property" 

349 

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

351 return "builtins.property" 

352 

353 def infer_call_result( 

354 self, 

355 caller: SuccessfulInferenceResult | None, 

356 context: InferenceContext | None = None, 

357 ) -> NoReturn: 

358 raise InferenceError("Properties are not callable") 

359 

360 def _infer( 

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

362 ) -> Generator[_T]: 

363 yield self