Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/astroid/objects.py: 37%
164 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:53 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:53 +0000
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, decorators, 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 if not mro_type.newstyle:
113 raise SuperError("Unable to call super on old-style classes.", super_=self)
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 )
123 index = mro.index(self.mro_pointer)
124 return mro[index + 1 :]
126 @cached_property
127 def _proxied(self):
128 ast_builtins = AstroidManager().builtins_module
129 return ast_builtins.getattr("super")[0]
131 def pytype(self) -> Literal["builtins.super"]:
132 return "builtins.super"
134 def display_type(self) -> str:
135 return "Super of"
137 @property
138 def name(self):
139 """Get the name of the MRO pointer."""
140 return self.mro_pointer.name
142 def qname(self) -> Literal["super"]:
143 return "super"
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
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
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
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)
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
224 if not found:
225 raise AttributeInferenceError(target=self, attribute=name, context=context)
227 def getattr(self, name, context: InferenceContext | None = None):
228 return list(self.igetattr(name, context=context))
231class ExceptionInstance(bases.Instance):
232 """Class for instances of exceptions.
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 """
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)
248class DictInstance(bases.Instance):
249 """Special kind of instances for dictionaries.
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 """
255 special_attributes = objectmodel.DictModel()
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__
266class DictKeys(bases.Proxy):
267 __str__ = node_classes.NodeNG.__str__
268 __repr__ = node_classes.NodeNG.__repr__
271class DictValues(bases.Proxy):
272 __str__ = node_classes.NodeNG.__str__
273 __repr__ = node_classes.NodeNG.__repr__
276class PartialFunction(scoped_nodes.FunctionDef):
277 """A class representing partial function obtained via functools.partial."""
279 @decorators.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead")
280 def __init__(
281 self, call, name=None, doc=None, lineno=None, col_offset=None, parent=None
282 ):
283 # TODO: Pass end_lineno, end_col_offset and parent as well
284 super().__init__(
285 name,
286 lineno=lineno,
287 col_offset=col_offset,
288 parent=node_classes.Unknown(),
289 end_col_offset=0,
290 end_lineno=0,
291 )
292 # Assigned directly to prevent triggering the DeprecationWarning.
293 self._doc = doc
294 # A typical FunctionDef automatically adds its name to the parent scope,
295 # but a partial should not, so defer setting parent until after init
296 self.parent = parent
297 self.filled_args = call.positional_arguments[1:]
298 self.filled_keywords = call.keyword_arguments
300 wrapped_function = call.positional_arguments[0]
301 inferred_wrapped_function = next(wrapped_function.infer())
302 if isinstance(inferred_wrapped_function, PartialFunction):
303 self.filled_args = inferred_wrapped_function.filled_args + self.filled_args
304 self.filled_keywords = {
305 **inferred_wrapped_function.filled_keywords,
306 **self.filled_keywords,
307 }
309 self.filled_positionals = len(self.filled_args)
311 def infer_call_result(
312 self,
313 caller: SuccessfulInferenceResult | None,
314 context: InferenceContext | None = None,
315 ) -> Iterator[InferenceResult]:
316 if context:
317 assert (
318 context.callcontext
319 ), "CallContext should be set before inferring call result"
320 current_passed_keywords = {
321 keyword for (keyword, _) in context.callcontext.keywords
322 }
323 for keyword, value in self.filled_keywords.items():
324 if keyword not in current_passed_keywords:
325 context.callcontext.keywords.append((keyword, value))
327 call_context_args = context.callcontext.args or []
328 context.callcontext.args = self.filled_args + call_context_args
330 return super().infer_call_result(caller=caller, context=context)
332 def qname(self) -> str:
333 return self.__class__.__name__
336# TODO: Hack to solve the circular import problem between node_classes and objects
337# This is not needed in 2.0, which has a cleaner design overall
338node_classes.Dict.__bases__ = (node_classes.NodeNG, DictInstance)
341class Property(scoped_nodes.FunctionDef):
342 """Class representing a Python property."""
344 @decorators.deprecate_arguments(doc="Use the postinit arg 'doc_node' instead")
345 def __init__(
346 self, function, name=None, doc=None, lineno=None, col_offset=None, parent=None
347 ):
348 self.function = function
349 super().__init__(
350 name,
351 lineno=lineno,
352 col_offset=col_offset,
353 parent=parent,
354 end_col_offset=function.end_col_offset,
355 end_lineno=function.end_lineno,
356 )
357 # Assigned directly to prevent triggering the DeprecationWarning.
358 self._doc = doc
360 special_attributes = objectmodel.PropertyModel()
361 type = "property"
363 def pytype(self) -> Literal["builtins.property"]:
364 return "builtins.property"
366 def infer_call_result(
367 self,
368 caller: SuccessfulInferenceResult | None,
369 context: InferenceContext | None = None,
370 ) -> NoReturn:
371 raise InferenceError("Properties are not callable")
373 def _infer(
374 self: _T, context: InferenceContext | None = None, **kwargs: Any
375 ) -> Generator[_T, None, None]:
376 yield self