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
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
16from collections.abc import Generator, Iterator
17from functools import cached_property
18from typing import Any, Literal, NoReturn, TypeVar
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
33_T = TypeVar("_T")
36class FrozenSet(node_classes.BaseContainer):
37 """Class representing a FrozenSet composite node."""
39 def pytype(self) -> Literal["builtins.frozenset"]:
40 return "builtins.frozenset"
42 def _infer(self, context: InferenceContext | None = None, **kwargs: Any):
43 yield self
45 @cached_property
46 def _proxied(self): # pylint: disable=method-hidden
47 ast_builtins = AstroidManager().builtins_module
48 return ast_builtins.getattr("frozenset")[0]
51class Super(node_classes.NodeNG):
52 """Proxy class over a super call.
54 This class offers almost the same behaviour as Python's super,
55 which is MRO lookups for retrieving attributes from the parents.
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 """
64 special_attributes = objectmodel.SuperModel()
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 )
87 def _infer(self, context: InferenceContext | None = None, **kwargs: Any):
88 yield self
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 )
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 )
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 )
120 index = mro.index(self.mro_pointer)
121 return mro[index + 1 :]
123 @cached_property
124 def _proxied(self):
125 ast_builtins = AstroidManager().builtins_module
126 return ast_builtins.getattr("super")[0]
128 def pytype(self) -> Literal["builtins.super"]:
129 return "builtins.super"
131 def display_type(self) -> str:
132 return "Super of"
134 @property
135 def name(self):
136 """Get the name of the MRO pointer."""
137 return self.mro_pointer.name
139 def qname(self) -> Literal["super"]:
140 return "super"
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
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
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
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)
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
221 if not found:
222 raise AttributeInferenceError(target=self, attribute=name, context=context)
224 def getattr(self, name, context: InferenceContext | None = None):
225 return list(self.igetattr(name, context=context))
228class ExceptionInstance(bases.Instance):
229 """Class for instances of exceptions.
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 """
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)
245class DictInstance(bases.Instance):
246 """Special kind of instances for dictionaries.
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 """
252 special_attributes = objectmodel.DictModel()
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__
263class DictKeys(bases.Proxy):
264 __str__ = node_classes.NodeNG.__str__
265 __repr__ = node_classes.NodeNG.__repr__
268class DictValues(bases.Proxy):
269 __str__ = node_classes.NodeNG.__str__
270 __repr__ = node_classes.NodeNG.__repr__
273class PartialFunction(scoped_nodes.FunctionDef):
274 """A class representing partial function obtained via functools.partial."""
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
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 }
301 self.filled_positionals = len(self.filled_args)
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))
319 call_context_args = context.callcontext.args or []
320 context.callcontext.args = self.filled_args + call_context_args
322 return super().infer_call_result(caller=caller, context=context)
324 def qname(self) -> str:
325 return self.__class__.__name__
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)
333class Property(scoped_nodes.FunctionDef):
334 """Class representing a Python property."""
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 )
347 special_attributes = objectmodel.PropertyModel()
348 type = "property"
350 def pytype(self) -> Literal["builtins.property"]:
351 return "builtins.property"
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")
360 def _infer(
361 self: _T, context: InferenceContext | None = None, **kwargs: Any
362 ) -> Generator[_T]:
363 yield self