Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/astroid/protocols.py: 41%
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"""This module contains a set of functions to handle python protocols for nodes
6where it makes sense.
7"""
9from __future__ import annotations
11import collections
12import itertools
13import operator as operator_mod
14from collections.abc import Callable, Generator, Iterator, Sequence
15from typing import TYPE_CHECKING, Any, TypeVar
17from astroid import bases, decorators, nodes, util
18from astroid.builder import extract_node
19from astroid.const import Context
20from astroid.context import InferenceContext, copy_context
21from astroid.exceptions import (
22 AstroidIndexError,
23 AstroidTypeError,
24 AttributeInferenceError,
25 InferenceError,
26 NoDefault,
27)
28from astroid.nodes import node_classes
29from astroid.typing import (
30 ConstFactoryResult,
31 InferenceResult,
32 SuccessfulInferenceResult,
33)
35if TYPE_CHECKING:
36 _TupleListNodeT = TypeVar("_TupleListNodeT", nodes.Tuple, nodes.List)
38_CONTEXTLIB_MGR = "contextlib.contextmanager"
40_UNARY_OPERATORS: dict[str, Callable[[Any], Any]] = {
41 "+": operator_mod.pos,
42 "-": operator_mod.neg,
43 "~": operator_mod.invert,
44 "not": operator_mod.not_,
45}
48def _infer_unary_op(obj: Any, op: str) -> ConstFactoryResult:
49 """Perform unary operation on `obj`, unless it is `NotImplemented`.
51 Can raise TypeError if operation is unsupported.
52 """
53 if obj is NotImplemented:
54 value = obj
55 else:
56 func = _UNARY_OPERATORS[op]
57 value = func(obj)
58 return nodes.const_factory(value)
61def tuple_infer_unary_op(self, op):
62 return _infer_unary_op(tuple(self.elts), op)
65def list_infer_unary_op(self, op):
66 return _infer_unary_op(self.elts, op)
69def set_infer_unary_op(self, op):
70 return _infer_unary_op(set(self.elts), op)
73def const_infer_unary_op(self, op):
74 return _infer_unary_op(self.value, op)
77def dict_infer_unary_op(self, op):
78 return _infer_unary_op(dict(self.items), op)
81# Binary operations
83BIN_OP_IMPL = {
84 "+": lambda a, b: a + b,
85 "-": lambda a, b: a - b,
86 "/": lambda a, b: a / b,
87 "//": lambda a, b: a // b,
88 "*": lambda a, b: a * b,
89 "**": lambda a, b: a**b,
90 "%": lambda a, b: a % b,
91 "&": lambda a, b: a & b,
92 "|": lambda a, b: a | b,
93 "^": lambda a, b: a ^ b,
94 "<<": lambda a, b: a << b,
95 ">>": lambda a, b: a >> b,
96 "@": operator_mod.matmul,
97}
98for _KEY, _IMPL in list(BIN_OP_IMPL.items()):
99 BIN_OP_IMPL[_KEY + "="] = _IMPL
102@decorators.yes_if_nothing_inferred
103def const_infer_binary_op(
104 self: nodes.Const,
105 opnode: nodes.AugAssign | nodes.BinOp,
106 operator: str,
107 other: InferenceResult,
108 context: InferenceContext,
109 _: SuccessfulInferenceResult,
110) -> Generator[ConstFactoryResult | util.UninferableBase]:
111 not_implemented = nodes.Const(NotImplemented)
112 if isinstance(other, nodes.Const):
113 if (
114 operator == "**"
115 and isinstance(self.value, (int, float))
116 and isinstance(other.value, (int, float))
117 and (self.value > 1e5 or other.value > 1e5)
118 ):
119 yield not_implemented
120 return
121 # Repeating a str/bytes by a large count ("x" * n or n * "x") would
122 # eagerly build the whole object, the same way list/tuple repetition
123 # is bounded in _multiply_seq_by_int. Don't materialize it.
124 if operator == "*":
125 sequence, count = self.value, other.value
126 if isinstance(sequence, int) and isinstance(count, (str, bytes)):
127 sequence, count = count, sequence
128 if (
129 isinstance(sequence, (str, bytes))
130 and isinstance(count, int)
131 and len(sequence) * count > 1e8
132 ):
133 yield util.Uninferable
134 return
135 # Left-shifting by a large amount builds an enormous int, the integer
136 # analog of the ** guard above.
137 if (
138 operator == "<<"
139 and isinstance(self.value, int)
140 and isinstance(other.value, int)
141 and other.value > 1e8
142 ):
143 yield util.Uninferable
144 return
145 try:
146 impl = BIN_OP_IMPL[operator]
147 try:
148 yield nodes.const_factory(impl(self.value, other.value))
149 except TypeError:
150 # ArithmeticError is not enough: float >> float is a TypeError
151 yield not_implemented
152 except Exception: # pylint: disable=broad-except
153 yield util.Uninferable
154 except TypeError:
155 yield not_implemented
156 elif isinstance(self.value, str) and operator == "%":
157 # TODO(cpopa): implement string interpolation later on.
158 yield util.Uninferable
159 else:
160 yield not_implemented
163def _multiply_seq_by_int(
164 self: _TupleListNodeT,
165 opnode: nodes.AugAssign | nodes.BinOp,
166 value: int,
167 context: InferenceContext,
168) -> _TupleListNodeT:
169 node = self.__class__(parent=opnode)
170 if not (value > 0 and self.elts):
171 node.elts = []
172 return node
173 if len(self.elts) * value > 1e8:
174 node.elts = [util.Uninferable]
175 return node
176 filtered_elts = (
177 util.safe_infer(elt, context) or util.Uninferable
178 for elt in self.elts
179 if not isinstance(elt, util.UninferableBase)
180 )
181 node.elts = list(filtered_elts) * value
182 return node
185def _filter_uninferable_nodes(
186 elts: Sequence[InferenceResult], context: InferenceContext
187) -> Iterator[SuccessfulInferenceResult]:
188 for elt in elts:
189 if isinstance(elt, util.UninferableBase):
190 yield node_classes.UNATTACHED_UNKNOWN
191 else:
192 for inferred in elt.infer(context):
193 if not isinstance(inferred, util.UninferableBase):
194 yield inferred
195 else:
196 yield node_classes.UNATTACHED_UNKNOWN
199@decorators.yes_if_nothing_inferred
200def tl_infer_binary_op(
201 self: _TupleListNodeT,
202 opnode: nodes.AugAssign | nodes.BinOp,
203 operator: str,
204 other: InferenceResult,
205 context: InferenceContext,
206 method: SuccessfulInferenceResult,
207) -> Generator[_TupleListNodeT | nodes.Const | util.UninferableBase]:
208 """Infer a binary operation on a tuple or list.
210 The instance on which the binary operation is performed is a tuple
211 or list. This refers to the left-hand side of the operation, so:
212 'tuple() + 1' or '[] + A()'
213 """
214 from astroid import helpers # pylint: disable=import-outside-toplevel
216 # For tuples and list the boundnode is no longer the tuple or list instance
217 context.boundnode = None
218 not_implemented = nodes.Const(NotImplemented)
219 if isinstance(other, self.__class__) and operator == "+":
220 node = self.__class__(parent=opnode)
221 node.elts = list(
222 itertools.chain(
223 _filter_uninferable_nodes(self.elts, context),
224 _filter_uninferable_nodes(other.elts, context),
225 )
226 )
227 yield node
228 elif isinstance(other, nodes.Const) and operator == "*":
229 if not isinstance(other.value, int):
230 yield not_implemented
231 return
232 yield _multiply_seq_by_int(self, opnode, other.value, context)
233 elif isinstance(other, bases.Instance) and operator == "*":
234 # Verify if the instance supports __index__.
235 as_index = helpers.class_instance_as_index(other)
236 if not as_index:
237 yield util.Uninferable
238 elif not isinstance(as_index.value, int): # pragma: no cover
239 # already checked by class_instance_as_index() but faster than casting
240 raise AssertionError("Please open a bug report.")
241 else:
242 yield _multiply_seq_by_int(self, opnode, as_index.value, context)
243 else:
244 yield not_implemented
247@decorators.yes_if_nothing_inferred
248def instance_class_infer_binary_op(
249 self: nodes.ClassDef,
250 opnode: nodes.AugAssign | nodes.BinOp,
251 operator: str,
252 other: InferenceResult,
253 context: InferenceContext,
254 method: SuccessfulInferenceResult,
255) -> Generator[InferenceResult]:
256 return method.infer_call_result(self, context)
259# assignment ##################################################################
260# pylint: disable-next=pointless-string-statement
261"""The assigned_stmts method is responsible to return the assigned statement
262(e.g. not inferred) according to the assignment type.
264The `assign_path` argument is used to record the lhs path of the original node.
265For instance if we want assigned statements for 'c' in 'a, (b,c)', assign_path
266will be [1, 1] once arrived to the Assign node.
268The `context` argument is the current inference context which should be given
269to any intermediary inference necessary.
270"""
273def _resolve_looppart(parts, assign_path, context):
274 """Recursive function to resolve multiple assignments on loops."""
275 assign_path = assign_path[:]
276 index = assign_path.pop(0)
277 for part in parts:
278 if isinstance(part, util.UninferableBase):
279 continue
280 if not hasattr(part, "itered"):
281 continue
282 try:
283 itered = part.itered()
284 except TypeError:
285 continue
286 try:
287 if isinstance(itered[index], (nodes.Const, nodes.Name)):
288 itered = [part]
289 except IndexError:
290 pass
291 for stmt in itered:
292 index_node = nodes.Const(index)
293 try:
294 assigned = stmt.getitem(index_node, context)
295 except (AttributeError, AstroidTypeError, AstroidIndexError):
296 continue
297 if not assign_path:
298 # we achieved to resolved the assignment path,
299 # don't infer the last part
300 yield assigned
301 elif isinstance(assigned, util.UninferableBase):
302 break
303 else:
304 # we are not yet on the last part of the path
305 # search on each possibly inferred value
306 try:
307 yield from _resolve_looppart(
308 assigned.infer(context), assign_path, context
309 )
310 except InferenceError:
311 break
314@decorators.raise_if_nothing_inferred
315def for_assigned_stmts(
316 self: nodes.For | nodes.Comprehension,
317 node: node_classes.AssignedStmtsPossibleNode = None,
318 context: InferenceContext | None = None,
319 assign_path: list[int] | None = None,
320) -> Any:
321 if isinstance(self, nodes.AsyncFor) or getattr(self, "is_async", False):
322 # Skip inferring of async code for now
323 return {
324 "node": self,
325 "unknown": node,
326 "assign_path": assign_path,
327 "context": context,
328 }
329 if assign_path is None:
330 for lst in self.iter.infer(context):
331 if isinstance(lst, (nodes.Tuple, nodes.List)):
332 yield from lst.elts
333 else:
334 yield from _resolve_looppart(self.iter.infer(context), assign_path, context)
335 return {
336 "node": self,
337 "unknown": node,
338 "assign_path": assign_path,
339 "context": context,
340 }
343def sequence_assigned_stmts(
344 self: nodes.Tuple | nodes.List,
345 node: node_classes.AssignedStmtsPossibleNode = None,
346 context: InferenceContext | None = None,
347 assign_path: list[int] | None = None,
348) -> Any:
349 if assign_path is None:
350 assign_path = []
351 try:
352 index = self.elts.index(node) # type: ignore[arg-type]
353 except ValueError as exc:
354 raise InferenceError(
355 "Tried to retrieve a node {node!r} which does not exist",
356 node=self,
357 assign_path=assign_path,
358 context=context,
359 ) from exc
361 assign_path.insert(0, index)
362 return self.parent.assigned_stmts(
363 node=self, context=context, assign_path=assign_path
364 )
367def assend_assigned_stmts(
368 self: nodes.AssignName | nodes.AssignAttr,
369 node: node_classes.AssignedStmtsPossibleNode = None,
370 context: InferenceContext | None = None,
371 assign_path: list[int] | None = None,
372) -> Any:
373 return self.parent.assigned_stmts(node=self, context=context)
376def _arguments_infer_argname(
377 self, name: str | None, context: InferenceContext | None
378) -> Generator[InferenceResult]:
379 # arguments information may be missing, in which case we can't do anything
380 # more
381 from astroid import arguments # pylint: disable=import-outside-toplevel
383 if not self.arguments:
384 yield util.Uninferable
385 return
387 args = [arg for arg in self.arguments if arg.name not in [self.vararg, self.kwarg]]
388 functype = self.parent.type
389 # first argument of instance/class method
390 if (
391 args
392 and getattr(self.arguments[0], "name", None) == name
393 and functype != "staticmethod"
394 ):
395 cls = self.parent.parent.scope()
396 is_metaclass = isinstance(cls, nodes.ClassDef) and cls.type == "metaclass"
397 # If this is a metaclass, then the first argument will always
398 # be the class, not an instance.
399 if (
400 context
401 and context.boundnode
402 and isinstance(context.boundnode, bases.Instance)
403 ):
404 cls = context.boundnode._proxied
405 if is_metaclass or functype == "classmethod":
406 yield cls
407 return
408 if functype == "method":
409 yield cls.instantiate_class()
410 return
412 if context and context.callcontext:
413 callee = context.callcontext.callee
414 while hasattr(callee, "_proxied"):
415 callee = callee._proxied
416 if getattr(callee, "name", None) == self.parent.name:
417 call_site = arguments.CallSite(context.callcontext, context.extra_context)
418 yield from call_site.infer_argument(self.parent, name, context)
419 return
421 if name == self.vararg:
422 vararg = nodes.const_factory(())
423 vararg.parent = self
424 if not args and self.parent.name == "__init__":
425 cls = self.parent.parent.scope()
426 vararg.elts = [cls.instantiate_class()]
427 yield vararg
428 return
429 if name == self.kwarg:
430 kwarg = nodes.const_factory({})
431 kwarg.parent = self
432 yield kwarg
433 return
434 # if there is a default value, yield it. And then yield Uninferable to reflect
435 # we can't guess given argument value
436 try:
437 context = copy_context(context)
438 yield from self.default_value(name).infer(context)
439 yield util.Uninferable
440 except NoDefault:
441 yield util.Uninferable
444def arguments_assigned_stmts(
445 self: nodes.Arguments,
446 node: node_classes.AssignedStmtsPossibleNode = None,
447 context: InferenceContext | None = None,
448 assign_path: list[int] | None = None,
449) -> Any:
450 from astroid import arguments # pylint: disable=import-outside-toplevel
452 try:
453 node_name = node.name # type: ignore[union-attr]
454 except AttributeError:
455 # Added to handle edge cases where node.name is not defined.
456 # https://github.com/pylint-dev/astroid/pull/1644#discussion_r901545816
457 node_name = None # pragma: no cover
459 if context and context.callcontext:
460 callee = context.callcontext.callee
461 while hasattr(callee, "_proxied"):
462 callee = callee._proxied
463 else:
464 return _arguments_infer_argname(self, node_name, context)
465 if node and getattr(callee, "name", None) == node.frame().name:
466 # reset call context/name
467 callcontext = context.callcontext
468 context = copy_context(context)
469 context.callcontext = None
470 args = arguments.CallSite(callcontext, context=context)
471 return args.infer_argument(self.parent, node_name, context)
472 return _arguments_infer_argname(self, node_name, context)
475@decorators.raise_if_nothing_inferred
476def assign_assigned_stmts(
477 self: nodes.AugAssign | nodes.Assign | nodes.AnnAssign | nodes.TypeAlias,
478 node: node_classes.AssignedStmtsPossibleNode = None,
479 context: InferenceContext | None = None,
480 assign_path: list[int] | None = None,
481) -> Any:
482 if not assign_path:
483 yield self.value
484 return None
485 yield from _resolve_assignment_parts(
486 self.value.infer(context), assign_path, context
487 )
489 return {
490 "node": self,
491 "unknown": node,
492 "assign_path": assign_path,
493 "context": context,
494 }
497def assign_annassigned_stmts(
498 self: nodes.AnnAssign,
499 node: node_classes.AssignedStmtsPossibleNode = None,
500 context: InferenceContext | None = None,
501 assign_path: list[int] | None = None,
502) -> Any:
503 for inferred in assign_assigned_stmts(self, node, context, assign_path):
504 if inferred is None:
505 yield util.Uninferable
506 else:
507 yield inferred
510def _resolve_assignment_parts(parts, assign_path, context):
511 """Recursive function to resolve multiple assignments."""
512 assign_path = assign_path[:]
513 index = assign_path.pop(0)
514 for part in parts:
515 assigned = None
516 if isinstance(part, nodes.Dict):
517 # A dictionary in an iterating context
518 try:
519 assigned, _ = part.items[index]
520 except IndexError:
521 return
523 elif hasattr(part, "getitem"):
524 index_node = nodes.Const(index)
525 try:
526 assigned = part.getitem(index_node, context)
527 except (AstroidTypeError, AstroidIndexError):
528 return
530 if not assigned:
531 return
533 if not assign_path:
534 # we achieved to resolved the assignment path, don't infer the
535 # last part
536 yield assigned
537 elif isinstance(assigned, util.UninferableBase):
538 return
539 else:
540 # we are not yet on the last part of the path search on each
541 # possibly inferred value
542 try:
543 yield from _resolve_assignment_parts(
544 assigned.infer(context), assign_path, context
545 )
546 except InferenceError:
547 return
550@decorators.raise_if_nothing_inferred
551def excepthandler_assigned_stmts(
552 self: nodes.ExceptHandler,
553 node: node_classes.AssignedStmtsPossibleNode = None,
554 context: InferenceContext | None = None,
555 assign_path: list[int] | None = None,
556) -> Any:
557 from astroid import objects # pylint: disable=import-outside-toplevel
559 def _generate_assigned():
560 for assigned in node_classes.unpack_infer(self.type):
561 if isinstance(assigned, nodes.ClassDef):
562 assigned = objects.ExceptionInstance(assigned)
564 yield assigned
566 if isinstance(self.parent, node_classes.TryStar):
567 # except * handler has assigned ExceptionGroup with caught
568 # exceptions under exceptions attribute
569 # pylint: disable-next=stop-iteration-return
570 eg = next(node_classes.unpack_infer(extract_node("""
571from builtins import ExceptionGroup
572ExceptionGroup
573""")))
574 assigned = objects.ExceptionInstance(eg)
575 assigned.instance_attrs["exceptions"] = [
576 nodes.Tuple.from_elements(_generate_assigned())
577 ]
578 yield assigned
579 else:
580 yield from _generate_assigned()
581 return {
582 "node": self,
583 "unknown": node,
584 "assign_path": assign_path,
585 "context": context,
586 }
589def _infer_context_manager(self, mgr, context):
590 try:
591 inferred = next(mgr.infer(context=context))
592 except StopIteration as e:
593 raise InferenceError(node=mgr) from e
594 if isinstance(inferred, bases.Generator):
595 # Check if it is decorated with contextlib.contextmanager.
596 func = inferred.parent
597 if not func.decorators:
598 raise InferenceError(
599 "No decorators found on inferred generator %s", node=func
600 )
602 for decorator_node in func.decorators.nodes:
603 decorator = next(decorator_node.infer(context=context), None)
604 if isinstance(decorator, nodes.FunctionDef):
605 if decorator.qname() == _CONTEXTLIB_MGR:
606 break
607 else:
608 # It doesn't interest us.
609 raise InferenceError(node=func)
610 try:
611 yield next(inferred.infer_yield_types())
612 except StopIteration as e:
613 raise InferenceError(node=func) from e
615 elif isinstance(inferred, bases.Instance):
616 try:
617 enter = next(inferred.igetattr("__enter__", context=context))
618 except (InferenceError, AttributeInferenceError, StopIteration) as exc:
619 raise InferenceError(node=inferred) from exc
620 if not isinstance(enter, bases.BoundMethod):
621 raise InferenceError(node=enter)
622 yield from enter.infer_call_result(self, context)
623 else:
624 raise InferenceError(node=mgr)
627@decorators.raise_if_nothing_inferred
628def with_assigned_stmts(
629 self: nodes.With,
630 node: node_classes.AssignedStmtsPossibleNode = None,
631 context: InferenceContext | None = None,
632 assign_path: list[int] | None = None,
633) -> Any:
634 """Infer names and other nodes from a *with* statement.
636 This enables only inference for name binding in a *with* statement.
637 For instance, in the following code, inferring `func` will return
638 the `ContextManager` class, not whatever ``__enter__`` returns.
639 We are doing this intentionally, because we consider that the context
640 manager result is whatever __enter__ returns and what it is binded
641 using the ``as`` keyword.
643 class ContextManager(object):
644 def __enter__(self):
645 return 42
646 with ContextManager() as f:
647 pass
649 # ContextManager().infer() will return ContextManager
650 # f.infer() will return 42.
652 Arguments:
653 self: nodes.With
654 node: The target of the assignment, `as (a, b)` in `with foo as (a, b)`.
655 context: Inference context used for caching already inferred objects
656 assign_path:
657 A list of indices, where each index specifies what item to fetch from
658 the inference results.
659 """
660 try:
661 mgr = next(mgr for (mgr, vars) in self.items if vars == node)
662 except StopIteration:
663 return None
664 if assign_path is None:
665 yield from _infer_context_manager(self, mgr, context)
666 else:
667 for result in _infer_context_manager(self, mgr, context):
668 # Walk the assign_path and get the item at the final index.
669 obj = result
670 for index in assign_path:
671 if not hasattr(obj, "elts"):
672 raise InferenceError(
673 "Wrong type ({targets!r}) for {node!r} assignment",
674 node=self,
675 targets=node,
676 assign_path=assign_path,
677 context=context,
678 )
679 try:
680 obj = obj.elts[index]
681 except IndexError as exc:
682 raise InferenceError(
683 "Tried to infer a nonexistent target with index {index} "
684 "in {node!r}.",
685 node=self,
686 targets=node,
687 assign_path=assign_path,
688 context=context,
689 ) from exc
690 except TypeError as exc:
691 raise InferenceError(
692 "Tried to unpack a non-iterable value in {node!r}.",
693 node=self,
694 targets=node,
695 assign_path=assign_path,
696 context=context,
697 ) from exc
698 yield obj
699 return {
700 "node": self,
701 "unknown": node,
702 "assign_path": assign_path,
703 "context": context,
704 }
707@decorators.raise_if_nothing_inferred
708def named_expr_assigned_stmts(
709 self: nodes.NamedExpr,
710 node: node_classes.AssignedStmtsPossibleNode,
711 context: InferenceContext | None = None,
712 assign_path: list[int] | None = None,
713) -> Any:
714 """Infer names and other nodes from an assignment expression."""
715 if self.target == node:
716 yield from self.value.infer(context=context)
717 else:
718 raise InferenceError(
719 "Cannot infer NamedExpr node {node!r}",
720 node=self,
721 assign_path=assign_path,
722 context=context,
723 )
726@decorators.yes_if_nothing_inferred
727def starred_assigned_stmts( # noqa: C901
728 self: nodes.Starred,
729 node: node_classes.AssignedStmtsPossibleNode = None,
730 context: InferenceContext | None = None,
731 assign_path: list[int] | None = None,
732) -> Any:
733 """
734 Arguments:
735 self: nodes.Starred
736 node: a node related to the current underlying Node.
737 context: Inference context used for caching already inferred objects
738 assign_path:
739 A list of indices, where each index specifies what item to fetch from
740 the inference results.
741 """
743 # pylint: disable = too-many-locals, too-many-statements, too-many-branches
745 def _determine_starred_iteration_lookups(
746 starred: nodes.Starred, target: nodes.Tuple, lookups: list[tuple[int, int]]
747 ) -> None:
748 # Determine the lookups for the rhs of the iteration
749 itered = target.itered()
750 for index, element in enumerate(itered):
751 if isinstance(element, nodes.Starred) and element is starred:
752 lookups.append((index, len(itered)))
753 break
754 if isinstance(element, nodes.Tuple):
755 lookups.append((index, len(element.itered())))
756 _determine_starred_iteration_lookups(starred, element, lookups)
758 stmt = self.statement()
759 if not isinstance(stmt, (nodes.Assign, nodes.For)):
760 raise InferenceError(
761 "Statement {stmt!r} enclosing {node!r} must be an Assign or For node.",
762 node=self,
763 stmt=stmt,
764 unknown=node,
765 context=context,
766 )
768 if context is None:
769 context = InferenceContext()
771 if isinstance(stmt, nodes.Assign):
772 value = stmt.value
773 lhs = stmt.targets[0]
774 if not isinstance(lhs, nodes.BaseContainer):
775 yield util.Uninferable
776 return
778 if sum(1 for _ in lhs.nodes_of_class(nodes.Starred)) > 1:
779 raise InferenceError(
780 "Too many starred arguments in the assignment targets {lhs!r}.",
781 node=self,
782 targets=lhs,
783 unknown=node,
784 context=context,
785 )
787 try:
788 rhs = next(value.infer(context))
789 except (InferenceError, StopIteration):
790 yield util.Uninferable
791 return
792 if isinstance(rhs, util.UninferableBase) or not hasattr(rhs, "itered"):
793 yield util.Uninferable
794 return
796 try:
797 elts = collections.deque(rhs.itered()) # type: ignore[union-attr]
798 except TypeError:
799 yield util.Uninferable
800 return
802 # Unpack iteratively the values from the rhs of the assignment,
803 # until the find the starred node. What will remain will
804 # be the list of values which the Starred node will represent
805 # This is done in two steps, from left to right to remove
806 # anything before the starred node and from right to left
807 # to remove anything after the starred node.
809 for index, left_node in enumerate(lhs.elts):
810 if not isinstance(left_node, nodes.Starred):
811 if not elts:
812 break
813 elts.popleft()
814 continue
815 lhs_elts = collections.deque(reversed(lhs.elts[index:]))
816 for right_node in lhs_elts:
817 if not isinstance(right_node, nodes.Starred):
818 if not elts:
819 break
820 elts.pop()
821 continue
823 # We're done unpacking.
824 packed = nodes.List(
825 ctx=Context.Store,
826 parent=self,
827 lineno=lhs.lineno,
828 col_offset=lhs.col_offset,
829 )
830 packed.postinit(elts=list(elts))
831 yield packed
832 break
834 if isinstance(stmt, nodes.For):
835 try:
836 inferred_iterable = next(stmt.iter.infer(context=context))
837 except (InferenceError, StopIteration):
838 yield util.Uninferable
839 return
840 if isinstance(inferred_iterable, util.UninferableBase) or not hasattr(
841 inferred_iterable, "itered"
842 ):
843 yield util.Uninferable
844 return
845 try:
846 itered = inferred_iterable.itered() # type: ignore[union-attr]
847 except TypeError:
848 yield util.Uninferable
849 return
851 target = stmt.target
853 if not isinstance(target, nodes.Tuple):
854 raise InferenceError(
855 f"Could not make sense of this, the target must be a tuple, not {type(target)!r}",
856 context=context,
857 )
859 lookups: list[tuple[int, int]] = []
860 _determine_starred_iteration_lookups(self, target, lookups)
861 if not lookups:
862 raise InferenceError(
863 "Could not make sense of this, needs at least a lookup", context=context
864 )
866 # Make the last lookup a slice, since that what we want for a Starred node
867 last_element_index, last_element_length = lookups[-1]
868 is_starred_last = last_element_index == (last_element_length - 1)
870 lookup_slice = slice(
871 last_element_index,
872 None if is_starred_last else (last_element_length - last_element_index),
873 )
874 last_lookup = lookup_slice
876 for element in itered:
877 # We probably want to infer the potential values *for each* element in an
878 # iterable, but we can't infer a list of all values, when only a list of
879 # step values are expected:
880 #
881 # for a, *b in [...]:
882 # b
883 #
884 # *b* should now point to just the elements at that particular iteration step,
885 # which astroid can't know about.
887 found_element = None
888 for index, lookup in enumerate(lookups):
889 if not hasattr(element, "itered"):
890 break
891 if index + 1 is len(lookups):
892 cur_lookup: slice | int = last_lookup
893 else:
894 # Grab just the index, not the whole length
895 cur_lookup = lookup[0]
896 try:
897 itered_inner_element = element.itered()
898 element = itered_inner_element[cur_lookup]
899 except IndexError:
900 break
901 except TypeError:
902 # Most likely the itered() call failed, cannot make sense of this
903 yield util.Uninferable
904 return
905 else:
906 found_element = element
908 unpacked = nodes.List(
909 ctx=Context.Store,
910 parent=self,
911 lineno=self.lineno,
912 col_offset=self.col_offset,
913 )
914 unpacked.postinit(elts=found_element or [])
915 yield unpacked
916 return
918 yield util.Uninferable
921@decorators.yes_if_nothing_inferred
922def match_mapping_assigned_stmts(
923 self: nodes.MatchMapping,
924 node: nodes.AssignName,
925 context: InferenceContext | None = None,
926 assign_path: None = None,
927) -> Generator[nodes.NodeNG]:
928 """Return empty generator (return -> raises StopIteration) so inferred value
929 is Uninferable.
930 """
931 return
932 yield
935@decorators.yes_if_nothing_inferred
936def match_star_assigned_stmts(
937 self: nodes.MatchStar,
938 node: nodes.AssignName,
939 context: InferenceContext | None = None,
940 assign_path: None = None,
941) -> Generator[nodes.NodeNG]:
942 """Return empty generator (return -> raises StopIteration) so inferred value
943 is Uninferable.
944 """
945 return
946 yield
949@decorators.yes_if_nothing_inferred
950def match_as_assigned_stmts(
951 self: nodes.MatchAs,
952 node: nodes.AssignName,
953 context: InferenceContext | None = None,
954 assign_path: None = None,
955) -> Generator[nodes.NodeNG]:
956 """Infer MatchAs as the Match subject if it's the only MatchCase pattern
957 else raise StopIteration to yield Uninferable.
958 """
959 if (
960 isinstance(self.parent, nodes.MatchCase)
961 and isinstance(self.parent.parent, nodes.Match)
962 and self.pattern is None
963 ):
964 yield self.parent.parent.subject
967@decorators.yes_if_nothing_inferred
968def generic_type_assigned_stmts(
969 self: nodes.TypeVar | nodes.TypeVarTuple | nodes.ParamSpec,
970 node: nodes.AssignName,
971 context: InferenceContext | None = None,
972 assign_path: None = None,
973) -> Generator[nodes.NodeNG]:
974 """Return the type parameter node itself so inference doesn't fail
975 when evaluating __class_getitem__ and so that the node's type is
976 preserved for downstream checks (e.g. TypeVarTuple starred handling).
977 """
978 yield self