protocols.py 32 KB

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