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

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

160 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 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 def __init__(self, call, name=None, lineno=None, col_offset=None, parent=None): 

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

281 super().__init__( 

282 name, 

283 lineno=lineno, 

284 col_offset=col_offset, 

285 parent=node_classes.Unknown(), 

286 end_col_offset=0, 

287 end_lineno=0, 

288 ) 

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

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

291 self.parent = parent 

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

293 self.filled_keywords = call.keyword_arguments 

294 

295 wrapped_function = call.positional_arguments[0] 

296 inferred_wrapped_function = next(wrapped_function.infer()) 

297 if isinstance(inferred_wrapped_function, PartialFunction): 

298 self.filled_args = inferred_wrapped_function.filled_args + self.filled_args 

299 self.filled_keywords = { 

300 **inferred_wrapped_function.filled_keywords, 

301 **self.filled_keywords, 

302 } 

303 

304 self.filled_positionals = len(self.filled_args) 

305 

306 def infer_call_result( 

307 self, 

308 caller: SuccessfulInferenceResult | None, 

309 context: InferenceContext | None = None, 

310 ) -> Iterator[InferenceResult]: 

311 if context: 

312 assert ( 

313 context.callcontext 

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

315 current_passed_keywords = { 

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

317 } 

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

319 if keyword not in current_passed_keywords: 

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

321 

322 call_context_args = context.callcontext.args or [] 

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

324 

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

326 

327 def qname(self) -> str: 

328 return self.__class__.__name__ 

329 

330 

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

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

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

334 

335 

336class Property(scoped_nodes.FunctionDef): 

337 """Class representing a Python property.""" 

338 

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

340 self.function = function 

341 super().__init__( 

342 name, 

343 lineno=lineno, 

344 col_offset=col_offset, 

345 parent=parent, 

346 end_col_offset=function.end_col_offset, 

347 end_lineno=function.end_lineno, 

348 ) 

349 

350 special_attributes = objectmodel.PropertyModel() 

351 type = "property" 

352 

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

354 return "builtins.property" 

355 

356 def infer_call_result( 

357 self, 

358 caller: SuccessfulInferenceResult | None, 

359 context: InferenceContext | None = None, 

360 ) -> NoReturn: 

361 raise InferenceError("Properties are not callable") 

362 

363 def _infer( 

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

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

366 yield self