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
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
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
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:
11 Call(func=Name('frozenset'), args=Tuple(...))
12"""
14from __future__ import annotations
16import sys
17from collections.abc import Generator, Iterator
18from functools import cached_property
19from typing import Literal, NoReturn
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
34if sys.version_info >= (3, 11):
35 from typing import Self
36else:
37 from typing_extensions import Self
40class FrozenSet(node_classes.BaseContainer):
41 """Class representing a FrozenSet composite node."""
43 def pytype(self) -> Literal["builtins.frozenset"]:
44 return "builtins.frozenset"
46 def _infer(self, context: InferenceContext | None = None):
47 yield self
49 @cached_property
50 def _proxied(self):
51 ast_builtins = AstroidManager().builtins_module
52 return ast_builtins.getattr("frozenset")[0]
55class Super(node_classes.NodeNG):
56 """Proxy class over a super call.
58 This class offers almost the same behaviour as Python's super,
59 which is MRO lookups for retrieving attributes from the parents.
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 """
68 special_attributes = objectmodel.SuperModel()
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 )
91 def _infer(self, context: InferenceContext | None = None):
92 yield self
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 )
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 )
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 )
124 index = mro.index(self.mro_pointer)
125 return mro[index + 1 :]
127 @cached_property
128 def _proxied(self):
129 ast_builtins = AstroidManager().builtins_module
130 return ast_builtins.getattr("super")[0]
132 def pytype(self) -> Literal["builtins.super"]:
133 return "builtins.super"
135 def display_type(self) -> str:
136 return "Super of"
138 @property
139 def name(self):
140 """Get the name of the MRO pointer."""
141 return self.mro_pointer.name
143 def qname(self) -> Literal["super"]:
144 return "super"
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
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
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
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)
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
230 if not found:
231 raise AttributeInferenceError(target=self, attribute=name, context=context)
233 def getattr(self, name, context: InferenceContext | None = None):
234 return list(self.igetattr(name, context=context))
237class ExceptionInstance(bases.Instance):
238 """Class for instances of exceptions.
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 """
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)
254class DictInstance(bases.Instance):
255 """Special kind of instances for dictionaries.
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 """
261 special_attributes = objectmodel.DictModel()
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__
271class DictKeys(bases.Proxy):
272 __str__ = node_classes.NodeNG.__str__
273 __repr__ = node_classes.NodeNG.__repr__
276class DictValues(bases.Proxy):
277 __str__ = node_classes.NodeNG.__str__
278 __repr__ = node_classes.NodeNG.__repr__
281class PartialFunction(scoped_nodes.FunctionDef):
282 """A class representing partial function obtained via functools.partial."""
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
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 }
323 self.filled_positionals = len(self.filled_args)
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))
341 call_context_args = context.callcontext.args or []
342 context.callcontext.args = self.filled_args + call_context_args
344 return super().infer_call_result(caller=caller, context=context)
346 def qname(self) -> str:
347 return self.__class__.__name__
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)
355class Property(scoped_nodes.FunctionDef):
356 """Class representing a Python property."""
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 )
369 special_attributes = objectmodel.PropertyModel()
370 type = "property"
372 def pytype(self) -> Literal["builtins.property"]:
373 return "builtins.property"
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")
382 def _infer(self, context: InferenceContext | None = None) -> Generator[Self]:
383 yield self