Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/astroid/arguments.py: 33%
132 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
5from __future__ import annotations
7from astroid import nodes
8from astroid.bases import Instance
9from astroid.context import CallContext, InferenceContext
10from astroid.exceptions import InferenceError, NoDefault
11from astroid.helpers import safe_infer
12from astroid.typing import InferenceResult
13from astroid.util import Uninferable, UninferableBase
16class CallSite:
17 """Class for understanding arguments passed into a call site.
19 It needs a call context, which contains the arguments and the
20 keyword arguments that were passed into a given call site.
21 In order to infer what an argument represents, call :meth:`infer_argument`
22 with the corresponding function node and the argument name.
24 :param callcontext:
25 An instance of :class:`astroid.context.CallContext`, that holds
26 the arguments for the call site.
27 :param argument_context_map:
28 Additional contexts per node, passed in from :attr:`astroid.context.Context.extra_context`
29 :param context:
30 An instance of :class:`astroid.context.Context`.
31 """
33 def __init__(
34 self,
35 callcontext: CallContext,
36 argument_context_map=None,
37 context: InferenceContext | None = None,
38 ):
39 if argument_context_map is None:
40 argument_context_map = {}
41 self.argument_context_map = argument_context_map
42 args = callcontext.args
43 keywords = callcontext.keywords
44 self.duplicated_keywords: set[str] = set()
45 self._unpacked_args = self._unpack_args(args, context=context)
46 self._unpacked_kwargs = self._unpack_keywords(keywords, context=context)
48 self.positional_arguments = [
49 arg for arg in self._unpacked_args if not isinstance(arg, UninferableBase)
50 ]
51 self.keyword_arguments = {
52 key: value
53 for key, value in self._unpacked_kwargs.items()
54 if not isinstance(value, UninferableBase)
55 }
57 @classmethod
58 def from_call(cls, call_node, context: InferenceContext | None = None):
59 """Get a CallSite object from the given Call node.
61 context will be used to force a single inference path.
62 """
64 # Determine the callcontext from the given `context` object if any.
65 context = context or InferenceContext()
66 callcontext = CallContext(call_node.args, call_node.keywords)
67 return cls(callcontext, context=context)
69 def has_invalid_arguments(self):
70 """Check if in the current CallSite were passed *invalid* arguments.
72 This can mean multiple things. For instance, if an unpacking
73 of an invalid object was passed, then this method will return True.
74 Other cases can be when the arguments can't be inferred by astroid,
75 for example, by passing objects which aren't known statically.
76 """
77 return len(self.positional_arguments) != len(self._unpacked_args)
79 def has_invalid_keywords(self) -> bool:
80 """Check if in the current CallSite were passed *invalid* keyword arguments.
82 For instance, unpacking a dictionary with integer keys is invalid
83 (**{1:2}), because the keys must be strings, which will make this
84 method to return True. Other cases where this might return True if
85 objects which can't be inferred were passed.
86 """
87 return len(self.keyword_arguments) != len(self._unpacked_kwargs)
89 def _unpack_keywords(self, keywords, context: InferenceContext | None = None):
90 values = {}
91 context = context or InferenceContext()
92 context.extra_context = self.argument_context_map
93 for name, value in keywords:
94 if name is None:
95 # Then it's an unpacking operation (**)
96 inferred = safe_infer(value, context=context)
97 if not isinstance(inferred, nodes.Dict):
98 # Not something we can work with.
99 values[name] = Uninferable
100 continue
102 for dict_key, dict_value in inferred.items:
103 dict_key = safe_infer(dict_key, context=context)
104 if not isinstance(dict_key, nodes.Const):
105 values[name] = Uninferable
106 continue
107 if not isinstance(dict_key.value, str):
108 values[name] = Uninferable
109 continue
110 if dict_key.value in values:
111 # The name is already in the dictionary
112 values[dict_key.value] = Uninferable
113 self.duplicated_keywords.add(dict_key.value)
114 continue
115 values[dict_key.value] = dict_value
116 else:
117 values[name] = value
118 return values
120 def _unpack_args(self, args, context: InferenceContext | None = None):
121 values = []
122 context = context or InferenceContext()
123 context.extra_context = self.argument_context_map
124 for arg in args:
125 if isinstance(arg, nodes.Starred):
126 inferred = safe_infer(arg.value, context=context)
127 if isinstance(inferred, UninferableBase):
128 values.append(Uninferable)
129 continue
130 if not hasattr(inferred, "elts"):
131 values.append(Uninferable)
132 continue
133 values.extend(inferred.elts)
134 else:
135 values.append(arg)
136 return values
138 def infer_argument(
139 self, funcnode: InferenceResult, name: str, context: InferenceContext
140 ): # noqa: C901
141 """Infer a function argument value according to the call context."""
142 if not isinstance(funcnode, (nodes.FunctionDef, nodes.Lambda)):
143 raise InferenceError(
144 f"Can not infer function argument value for non-function node {funcnode!r}.",
145 call_site=self,
146 func=funcnode,
147 arg=name,
148 context=context,
149 )
151 if name in self.duplicated_keywords:
152 raise InferenceError(
153 "The arguments passed to {func!r} have duplicate keywords.",
154 call_site=self,
155 func=funcnode,
156 arg=name,
157 context=context,
158 )
160 # Look into the keywords first, maybe it's already there.
161 try:
162 return self.keyword_arguments[name].infer(context)
163 except KeyError:
164 pass
166 # Too many arguments given and no variable arguments.
167 if len(self.positional_arguments) > len(funcnode.args.args):
168 if not funcnode.args.vararg and not funcnode.args.posonlyargs:
169 raise InferenceError(
170 "Too many positional arguments "
171 "passed to {func!r} that does "
172 "not have *args.",
173 call_site=self,
174 func=funcnode,
175 arg=name,
176 context=context,
177 )
179 positional = self.positional_arguments[: len(funcnode.args.args)]
180 vararg = self.positional_arguments[len(funcnode.args.args) :]
181 argindex = funcnode.args.find_argname(name)[0]
182 kwonlyargs = {arg.name for arg in funcnode.args.kwonlyargs}
183 kwargs = {
184 key: value
185 for key, value in self.keyword_arguments.items()
186 if key not in kwonlyargs
187 }
188 # If there are too few positionals compared to
189 # what the function expects to receive, check to see
190 # if the missing positional arguments were passed
191 # as keyword arguments and if so, place them into the
192 # positional args list.
193 if len(positional) < len(funcnode.args.args):
194 for func_arg in funcnode.args.args:
195 if func_arg.name in kwargs:
196 arg = kwargs.pop(func_arg.name)
197 positional.append(arg)
199 if argindex is not None:
200 boundnode = context.boundnode
201 # 2. first argument of instance/class method
202 if argindex == 0 and funcnode.type in {"method", "classmethod"}:
203 # context.boundnode is None when an instance method is called with
204 # the class, e.g. MyClass.method(obj, ...). In this case, self
205 # is the first argument.
206 if boundnode is None and funcnode.type == "method" and positional:
207 return positional[0].infer(context=context)
208 if boundnode is None:
209 # XXX can do better ?
210 boundnode = funcnode.parent.frame(future=True)
212 if isinstance(boundnode, nodes.ClassDef):
213 # Verify that we're accessing a method
214 # of the metaclass through a class, as in
215 # `cls.metaclass_method`. In this case, the
216 # first argument is always the class.
217 method_scope = funcnode.parent.scope()
218 if method_scope is boundnode.metaclass():
219 return iter((boundnode,))
221 if funcnode.type == "method":
222 if not isinstance(boundnode, Instance):
223 boundnode = boundnode.instantiate_class()
224 return iter((boundnode,))
225 if funcnode.type == "classmethod":
226 return iter((boundnode,))
227 # if we have a method, extract one position
228 # from the index, so we'll take in account
229 # the extra parameter represented by `self` or `cls`
230 if funcnode.type in {"method", "classmethod"} and boundnode:
231 argindex -= 1
232 # 2. search arg index
233 try:
234 return self.positional_arguments[argindex].infer(context)
235 except IndexError:
236 pass
238 if funcnode.args.kwarg == name:
239 # It wants all the keywords that were passed into
240 # the call site.
241 if self.has_invalid_keywords():
242 raise InferenceError(
243 "Inference failed to find values for all keyword arguments "
244 "to {func!r}: {unpacked_kwargs!r} doesn't correspond to "
245 "{keyword_arguments!r}.",
246 keyword_arguments=self.keyword_arguments,
247 unpacked_kwargs=self._unpacked_kwargs,
248 call_site=self,
249 func=funcnode,
250 arg=name,
251 context=context,
252 )
253 kwarg = nodes.Dict(
254 lineno=funcnode.args.lineno,
255 col_offset=funcnode.args.col_offset,
256 parent=funcnode.args,
257 end_lineno=funcnode.args.end_lineno,
258 end_col_offset=funcnode.args.end_col_offset,
259 )
260 kwarg.postinit(
261 [(nodes.const_factory(key), value) for key, value in kwargs.items()]
262 )
263 return iter((kwarg,))
264 if funcnode.args.vararg == name:
265 # It wants all the args that were passed into
266 # the call site.
267 if self.has_invalid_arguments():
268 raise InferenceError(
269 "Inference failed to find values for all positional "
270 "arguments to {func!r}: {unpacked_args!r} doesn't "
271 "correspond to {positional_arguments!r}.",
272 positional_arguments=self.positional_arguments,
273 unpacked_args=self._unpacked_args,
274 call_site=self,
275 func=funcnode,
276 arg=name,
277 context=context,
278 )
279 args = nodes.Tuple(
280 lineno=funcnode.args.lineno,
281 col_offset=funcnode.args.col_offset,
282 parent=funcnode.args,
283 )
284 args.postinit(vararg)
285 return iter((args,))
287 # Check if it's a default parameter.
288 try:
289 return funcnode.args.default_value(name).infer(context)
290 except NoDefault:
291 pass
292 raise InferenceError(
293 "No value found for argument {arg} to {func!r}",
294 call_site=self,
295 func=funcnode,
296 arg=name,
297 context=context,
298 )