inference.py 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278
  1. # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
  2. # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
  3. # Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt
  4. """This module contains a set of functions to handle inference on astroid trees."""
  5. from __future__ import annotations
  6. import ast
  7. import functools
  8. import itertools
  9. import operator
  10. import typing
  11. from collections.abc import Callable, Generator, Iterable, Iterator
  12. from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union
  13. from astroid import bases, constraint, decorators, helpers, nodes, protocols, util
  14. from astroid.const import PY310_PLUS
  15. from astroid.context import (
  16. CallContext,
  17. InferenceContext,
  18. bind_context_to_node,
  19. copy_context,
  20. )
  21. from astroid.exceptions import (
  22. AstroidBuildingError,
  23. AstroidError,
  24. AstroidIndexError,
  25. AstroidTypeError,
  26. AstroidValueError,
  27. AttributeInferenceError,
  28. InferenceError,
  29. NameInferenceError,
  30. _NonDeducibleTypeHierarchy,
  31. )
  32. from astroid.interpreter import dunder_lookup
  33. from astroid.manager import AstroidManager
  34. from astroid.typing import (
  35. InferenceErrorInfo,
  36. InferenceResult,
  37. SuccessfulInferenceResult,
  38. )
  39. if TYPE_CHECKING:
  40. from astroid.objects import Property
  41. # Prevents circular imports
  42. objects = util.lazy_import("objects")
  43. _T = TypeVar("_T")
  44. _BaseContainerT = TypeVar("_BaseContainerT", bound=nodes.BaseContainer)
  45. _FunctionDefT = TypeVar("_FunctionDefT", bound=nodes.FunctionDef)
  46. GetFlowFactory = typing.Callable[
  47. [
  48. InferenceResult,
  49. Optional[InferenceResult],
  50. Union[nodes.AugAssign, nodes.BinOp],
  51. InferenceResult,
  52. Optional[InferenceResult],
  53. InferenceContext,
  54. InferenceContext,
  55. ],
  56. "list[functools.partial[Generator[InferenceResult, None, None]]]",
  57. ]
  58. # .infer method ###############################################################
  59. def infer_end(
  60. self: _T, context: InferenceContext | None = None, **kwargs: Any
  61. ) -> Iterator[_T]:
  62. """Inference's end for nodes that yield themselves on inference.
  63. These are objects for which inference does not have any semantic,
  64. such as Module or Consts.
  65. """
  66. yield self
  67. # We add ignores to all assignments to methods
  68. # See https://github.com/python/mypy/issues/2427
  69. nodes.Module._infer = infer_end
  70. nodes.ClassDef._infer = infer_end
  71. nodes.Lambda._infer = infer_end # type: ignore[assignment]
  72. nodes.Const._infer = infer_end # type: ignore[assignment]
  73. nodes.Slice._infer = infer_end # type: ignore[assignment]
  74. def _infer_sequence_helper(
  75. node: _BaseContainerT, context: InferenceContext | None = None
  76. ) -> list[SuccessfulInferenceResult]:
  77. """Infer all values based on _BaseContainer.elts."""
  78. values = []
  79. for elt in node.elts:
  80. if isinstance(elt, nodes.Starred):
  81. starred = helpers.safe_infer(elt.value, context)
  82. if not starred:
  83. raise InferenceError(node=node, context=context)
  84. if not hasattr(starred, "elts"):
  85. raise InferenceError(node=node, context=context)
  86. values.extend(_infer_sequence_helper(starred))
  87. elif isinstance(elt, nodes.NamedExpr):
  88. value = helpers.safe_infer(elt.value, context)
  89. if not value:
  90. raise InferenceError(node=node, context=context)
  91. values.append(value)
  92. else:
  93. values.append(elt)
  94. return values
  95. @decorators.raise_if_nothing_inferred
  96. def infer_sequence(
  97. self: _BaseContainerT,
  98. context: InferenceContext | None = None,
  99. **kwargs: Any,
  100. ) -> Iterator[_BaseContainerT]:
  101. has_starred_named_expr = any(
  102. isinstance(e, (nodes.Starred, nodes.NamedExpr)) for e in self.elts
  103. )
  104. if has_starred_named_expr:
  105. values = _infer_sequence_helper(self, context)
  106. new_seq = type(self)(
  107. lineno=self.lineno, col_offset=self.col_offset, parent=self.parent
  108. )
  109. new_seq.postinit(values)
  110. yield new_seq
  111. else:
  112. yield self
  113. nodes.List._infer = infer_sequence # type: ignore[assignment]
  114. nodes.Tuple._infer = infer_sequence # type: ignore[assignment]
  115. nodes.Set._infer = infer_sequence # type: ignore[assignment]
  116. def infer_map(
  117. self: nodes.Dict, context: InferenceContext | None = None
  118. ) -> Iterator[nodes.Dict]:
  119. if not any(isinstance(k, nodes.DictUnpack) for k, _ in self.items):
  120. yield self
  121. else:
  122. items = _infer_map(self, context)
  123. new_seq = type(self)(self.lineno, self.col_offset, self.parent)
  124. new_seq.postinit(list(items.items()))
  125. yield new_seq
  126. def _update_with_replacement(
  127. lhs_dict: dict[SuccessfulInferenceResult, SuccessfulInferenceResult],
  128. rhs_dict: dict[SuccessfulInferenceResult, SuccessfulInferenceResult],
  129. ) -> dict[SuccessfulInferenceResult, SuccessfulInferenceResult]:
  130. """Delete nodes that equate to duplicate keys.
  131. Since an astroid node doesn't 'equal' another node with the same value,
  132. this function uses the as_string method to make sure duplicate keys
  133. don't get through
  134. Note that both the key and the value are astroid nodes
  135. Fixes issue with DictUnpack causing duplicate keys
  136. in inferred Dict items
  137. :param lhs_dict: Dictionary to 'merge' nodes into
  138. :param rhs_dict: Dictionary with nodes to pull from
  139. :return : merged dictionary of nodes
  140. """
  141. combined_dict = itertools.chain(lhs_dict.items(), rhs_dict.items())
  142. # Overwrite keys which have the same string values
  143. string_map = {key.as_string(): (key, value) for key, value in combined_dict}
  144. # Return to dictionary
  145. return dict(string_map.values())
  146. def _infer_map(
  147. node: nodes.Dict, context: InferenceContext | None
  148. ) -> dict[SuccessfulInferenceResult, SuccessfulInferenceResult]:
  149. """Infer all values based on Dict.items."""
  150. values: dict[SuccessfulInferenceResult, SuccessfulInferenceResult] = {}
  151. for name, value in node.items:
  152. if isinstance(name, nodes.DictUnpack):
  153. double_starred = helpers.safe_infer(value, context)
  154. if not double_starred:
  155. raise InferenceError
  156. if not isinstance(double_starred, nodes.Dict):
  157. raise InferenceError(node=node, context=context)
  158. unpack_items = _infer_map(double_starred, context)
  159. values = _update_with_replacement(values, unpack_items)
  160. else:
  161. key = helpers.safe_infer(name, context=context)
  162. safe_value = helpers.safe_infer(value, context=context)
  163. if any(not elem for elem in (key, safe_value)):
  164. raise InferenceError(node=node, context=context)
  165. # safe_value is SuccessfulInferenceResult as bool(Uninferable) == False
  166. values = _update_with_replacement(values, {key: safe_value})
  167. return values
  168. nodes.Dict._infer = infer_map # type: ignore[assignment]
  169. def _higher_function_scope(node: nodes.NodeNG) -> nodes.FunctionDef | None:
  170. """Search for the first function which encloses the given
  171. scope. This can be used for looking up in that function's
  172. scope, in case looking up in a lower scope for a particular
  173. name fails.
  174. :param node: A scope node.
  175. :returns:
  176. ``None``, if no parent function scope was found,
  177. otherwise an instance of :class:`astroid.nodes.scoped_nodes.Function`,
  178. which encloses the given node.
  179. """
  180. current = node
  181. while current.parent and not isinstance(current.parent, nodes.FunctionDef):
  182. current = current.parent
  183. if current and current.parent:
  184. return current.parent # type: ignore[no-any-return]
  185. return None
  186. def infer_name(
  187. self: nodes.Name | nodes.AssignName,
  188. context: InferenceContext | None = None,
  189. **kwargs: Any,
  190. ) -> Generator[InferenceResult, None, None]:
  191. """Infer a Name: use name lookup rules."""
  192. frame, stmts = self.lookup(self.name)
  193. if not stmts:
  194. # Try to see if the name is enclosed in a nested function
  195. # and use the higher (first function) scope for searching.
  196. parent_function = _higher_function_scope(self.scope())
  197. if parent_function:
  198. _, stmts = parent_function.lookup(self.name)
  199. if not stmts:
  200. raise NameInferenceError(
  201. name=self.name, scope=self.scope(), context=context
  202. )
  203. context = copy_context(context)
  204. context.lookupname = self.name
  205. context.constraints[self.name] = constraint.get_constraints(self, frame)
  206. return bases._infer_stmts(stmts, context, frame)
  207. # pylint: disable=no-value-for-parameter
  208. # The order of the decorators here is important
  209. # See https://github.com/PyCQA/astroid/commit/0a8a75db30da060a24922e05048bc270230f5
  210. nodes.Name._infer = decorators.raise_if_nothing_inferred(
  211. decorators.path_wrapper(infer_name)
  212. )
  213. nodes.AssignName.infer_lhs = infer_name # won't work with a path wrapper
  214. @decorators.raise_if_nothing_inferred
  215. @decorators.path_wrapper
  216. def infer_call(
  217. self: nodes.Call, context: InferenceContext | None = None, **kwargs: Any
  218. ) -> Generator[InferenceResult, None, InferenceErrorInfo]:
  219. """Infer a Call node by trying to guess what the function returns."""
  220. callcontext = copy_context(context)
  221. callcontext.boundnode = None
  222. if context is not None:
  223. callcontext.extra_context = _populate_context_lookup(self, context.clone())
  224. for callee in self.func.infer(context):
  225. if isinstance(callee, util.UninferableBase):
  226. yield callee
  227. continue
  228. try:
  229. if hasattr(callee, "infer_call_result"):
  230. callcontext.callcontext = CallContext(
  231. args=self.args, keywords=self.keywords, callee=callee
  232. )
  233. yield from callee.infer_call_result(caller=self, context=callcontext)
  234. except InferenceError:
  235. continue
  236. return InferenceErrorInfo(node=self, context=context)
  237. nodes.Call._infer = infer_call # type: ignore[assignment]
  238. @decorators.raise_if_nothing_inferred
  239. @decorators.path_wrapper
  240. def infer_import(
  241. self: nodes.Import,
  242. context: InferenceContext | None = None,
  243. asname: bool = True,
  244. **kwargs: Any,
  245. ) -> Generator[nodes.Module, None, None]:
  246. """Infer an Import node: return the imported module/object."""
  247. context = context or InferenceContext()
  248. name = context.lookupname
  249. if name is None:
  250. raise InferenceError(node=self, context=context)
  251. try:
  252. if asname:
  253. yield self.do_import_module(self.real_name(name))
  254. else:
  255. yield self.do_import_module(name)
  256. except AstroidBuildingError as exc:
  257. raise InferenceError(node=self, context=context) from exc
  258. nodes.Import._infer = infer_import
  259. @decorators.raise_if_nothing_inferred
  260. @decorators.path_wrapper
  261. def infer_import_from(
  262. self: nodes.ImportFrom,
  263. context: InferenceContext | None = None,
  264. asname: bool = True,
  265. **kwargs: Any,
  266. ) -> Generator[InferenceResult, None, None]:
  267. """Infer a ImportFrom node: return the imported module/object."""
  268. context = context or InferenceContext()
  269. name = context.lookupname
  270. if name is None:
  271. raise InferenceError(node=self, context=context)
  272. if asname:
  273. try:
  274. name = self.real_name(name)
  275. except AttributeInferenceError as exc:
  276. # See https://github.com/PyCQA/pylint/issues/4692
  277. raise InferenceError(node=self, context=context) from exc
  278. try:
  279. module = self.do_import_module()
  280. except AstroidBuildingError as exc:
  281. raise InferenceError(node=self, context=context) from exc
  282. try:
  283. context = copy_context(context)
  284. context.lookupname = name
  285. stmts = module.getattr(name, ignore_locals=module is self.root())
  286. return bases._infer_stmts(stmts, context)
  287. except AttributeInferenceError as error:
  288. raise InferenceError(
  289. str(error), target=self, attribute=name, context=context
  290. ) from error
  291. nodes.ImportFrom._infer = infer_import_from # type: ignore[assignment]
  292. def infer_attribute(
  293. self: nodes.Attribute | nodes.AssignAttr,
  294. context: InferenceContext | None = None,
  295. **kwargs: Any,
  296. ) -> Generator[InferenceResult, None, InferenceErrorInfo]:
  297. """Infer an Attribute node by using getattr on the associated object."""
  298. for owner in self.expr.infer(context):
  299. if isinstance(owner, util.UninferableBase):
  300. yield owner
  301. continue
  302. context = copy_context(context)
  303. old_boundnode = context.boundnode
  304. try:
  305. context.boundnode = owner
  306. if isinstance(owner, (nodes.ClassDef, bases.Instance)):
  307. frame = owner if isinstance(owner, nodes.ClassDef) else owner._proxied
  308. context.constraints[self.attrname] = constraint.get_constraints(
  309. self, frame=frame
  310. )
  311. if self.attrname == "argv" and owner.name == "sys":
  312. # sys.argv will never be inferable during static analysis
  313. # It's value would be the args passed to the linter itself
  314. yield util.Uninferable
  315. else:
  316. yield from owner.igetattr(self.attrname, context)
  317. except (
  318. AttributeInferenceError,
  319. InferenceError,
  320. AttributeError,
  321. ):
  322. pass
  323. finally:
  324. context.boundnode = old_boundnode
  325. return InferenceErrorInfo(node=self, context=context)
  326. # The order of the decorators here is important
  327. # See https://github.com/PyCQA/astroid/commit/0a8a75db30da060a24922e05048bc270230f5
  328. nodes.Attribute._infer = decorators.raise_if_nothing_inferred(
  329. decorators.path_wrapper(infer_attribute)
  330. )
  331. # won't work with a path wrapper
  332. nodes.AssignAttr.infer_lhs = decorators.raise_if_nothing_inferred(infer_attribute)
  333. @decorators.raise_if_nothing_inferred
  334. @decorators.path_wrapper
  335. def infer_global(
  336. self: nodes.Global, context: InferenceContext | None = None, **kwargs: Any
  337. ) -> Generator[InferenceResult, None, None]:
  338. if context is None or context.lookupname is None:
  339. raise InferenceError(node=self, context=context)
  340. try:
  341. return bases._infer_stmts(self.root().getattr(context.lookupname), context)
  342. except AttributeInferenceError as error:
  343. raise InferenceError(
  344. str(error), target=self, attribute=context.lookupname, context=context
  345. ) from error
  346. nodes.Global._infer = infer_global # type: ignore[assignment]
  347. _SUBSCRIPT_SENTINEL = object()
  348. def infer_subscript(
  349. self: nodes.Subscript, context: InferenceContext | None = None, **kwargs: Any
  350. ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
  351. """Inference for subscripts.
  352. We're understanding if the index is a Const
  353. or a slice, passing the result of inference
  354. to the value's `getitem` method, which should
  355. handle each supported index type accordingly.
  356. """
  357. found_one = False
  358. for value in self.value.infer(context):
  359. if isinstance(value, util.UninferableBase):
  360. yield util.Uninferable
  361. return None
  362. for index in self.slice.infer(context):
  363. if isinstance(index, util.UninferableBase):
  364. yield util.Uninferable
  365. return None
  366. # Try to deduce the index value.
  367. index_value = _SUBSCRIPT_SENTINEL
  368. if value.__class__ == bases.Instance:
  369. index_value = index
  370. elif index.__class__ == bases.Instance:
  371. instance_as_index = helpers.class_instance_as_index(index)
  372. if instance_as_index:
  373. index_value = instance_as_index
  374. else:
  375. index_value = index
  376. if index_value is _SUBSCRIPT_SENTINEL:
  377. raise InferenceError(node=self, context=context)
  378. try:
  379. assigned = value.getitem(index_value, context)
  380. except (
  381. AstroidTypeError,
  382. AstroidIndexError,
  383. AstroidValueError,
  384. AttributeInferenceError,
  385. AttributeError,
  386. ) as exc:
  387. raise InferenceError(node=self, context=context) from exc
  388. # Prevent inferring if the inferred subscript
  389. # is the same as the original subscripted object.
  390. if self is assigned or isinstance(assigned, util.UninferableBase):
  391. yield util.Uninferable
  392. return None
  393. yield from assigned.infer(context)
  394. found_one = True
  395. if found_one:
  396. return InferenceErrorInfo(node=self, context=context)
  397. return None
  398. # The order of the decorators here is important
  399. # See https://github.com/PyCQA/astroid/commit/0a8a75db30da060a24922e05048bc270230f5
  400. nodes.Subscript._infer = decorators.raise_if_nothing_inferred( # type: ignore[assignment]
  401. decorators.path_wrapper(infer_subscript)
  402. )
  403. nodes.Subscript.infer_lhs = decorators.raise_if_nothing_inferred(infer_subscript)
  404. @decorators.raise_if_nothing_inferred
  405. @decorators.path_wrapper
  406. def _infer_boolop(
  407. self: nodes.BoolOp, context: InferenceContext | None = None, **kwargs: Any
  408. ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
  409. """Infer a boolean operation (and / or / not).
  410. The function will calculate the boolean operation
  411. for all pairs generated through inference for each component
  412. node.
  413. """
  414. values = self.values
  415. if self.op == "or":
  416. predicate = operator.truth
  417. else:
  418. predicate = operator.not_
  419. try:
  420. inferred_values = [value.infer(context=context) for value in values]
  421. except InferenceError:
  422. yield util.Uninferable
  423. return None
  424. for pair in itertools.product(*inferred_values):
  425. if any(isinstance(item, util.UninferableBase) for item in pair):
  426. # Can't infer the final result, just yield Uninferable.
  427. yield util.Uninferable
  428. continue
  429. bool_values = [item.bool_value() for item in pair]
  430. if any(isinstance(item, util.UninferableBase) for item in bool_values):
  431. # Can't infer the final result, just yield Uninferable.
  432. yield util.Uninferable
  433. continue
  434. # Since the boolean operations are short circuited operations,
  435. # this code yields the first value for which the predicate is True
  436. # and if no value respected the predicate, then the last value will
  437. # be returned (or Uninferable if there was no last value).
  438. # This is conforming to the semantics of `and` and `or`:
  439. # 1 and 0 -> 1
  440. # 0 and 1 -> 0
  441. # 1 or 0 -> 1
  442. # 0 or 1 -> 1
  443. value = util.Uninferable
  444. for value, bool_value in zip(pair, bool_values):
  445. if predicate(bool_value):
  446. yield value
  447. break
  448. else:
  449. yield value
  450. return InferenceErrorInfo(node=self, context=context)
  451. nodes.BoolOp._infer = _infer_boolop
  452. # UnaryOp, BinOp and AugAssign inferences
  453. def _filter_operation_errors(
  454. self: _T,
  455. infer_callable: Callable[
  456. [_T, InferenceContext | None],
  457. Generator[InferenceResult | util.BadOperationMessage, None, None],
  458. ],
  459. context: InferenceContext | None,
  460. error: type[util.BadOperationMessage],
  461. ) -> Generator[InferenceResult, None, None]:
  462. for result in infer_callable(self, context):
  463. if isinstance(result, error):
  464. # For the sake of .infer(), we don't care about operation
  465. # errors, which is the job of pylint. So return something
  466. # which shows that we can't infer the result.
  467. yield util.Uninferable
  468. else:
  469. yield result
  470. def _infer_unaryop(
  471. self: nodes.UnaryOp, context: InferenceContext | None = None
  472. ) -> Generator[InferenceResult | util.BadUnaryOperationMessage, None, None]:
  473. """Infer what an UnaryOp should return when evaluated."""
  474. for operand in self.operand.infer(context):
  475. try:
  476. yield operand.infer_unary_op(self.op)
  477. except TypeError as exc:
  478. # The operand doesn't support this operation.
  479. yield util.BadUnaryOperationMessage(operand, self.op, exc)
  480. except AttributeError as exc:
  481. meth = protocols.UNARY_OP_METHOD[self.op]
  482. if meth is None:
  483. # `not node`. Determine node's boolean
  484. # value and negate its result, unless it is
  485. # Uninferable, which will be returned as is.
  486. bool_value = operand.bool_value()
  487. if not isinstance(bool_value, util.UninferableBase):
  488. yield nodes.const_factory(not bool_value)
  489. else:
  490. yield util.Uninferable
  491. else:
  492. if not isinstance(operand, (bases.Instance, nodes.ClassDef)):
  493. # The operation was used on something which
  494. # doesn't support it.
  495. yield util.BadUnaryOperationMessage(operand, self.op, exc)
  496. continue
  497. try:
  498. try:
  499. methods = dunder_lookup.lookup(operand, meth)
  500. except AttributeInferenceError:
  501. yield util.BadUnaryOperationMessage(operand, self.op, exc)
  502. continue
  503. meth = methods[0]
  504. inferred = next(meth.infer(context=context), None)
  505. if (
  506. isinstance(inferred, util.UninferableBase)
  507. or not inferred.callable()
  508. ):
  509. continue
  510. context = copy_context(context)
  511. context.boundnode = operand
  512. context.callcontext = CallContext(args=[], callee=inferred)
  513. call_results = inferred.infer_call_result(self, context=context)
  514. result = next(call_results, None)
  515. if result is None:
  516. # Failed to infer, return the same type.
  517. yield operand
  518. else:
  519. yield result
  520. except AttributeInferenceError as inner_exc:
  521. # The unary operation special method was not found.
  522. yield util.BadUnaryOperationMessage(operand, self.op, inner_exc)
  523. except InferenceError:
  524. yield util.Uninferable
  525. @decorators.raise_if_nothing_inferred
  526. @decorators.path_wrapper
  527. def infer_unaryop(
  528. self: nodes.UnaryOp, context: InferenceContext | None = None, **kwargs: Any
  529. ) -> Generator[InferenceResult, None, InferenceErrorInfo]:
  530. """Infer what an UnaryOp should return when evaluated."""
  531. yield from _filter_operation_errors(
  532. self, _infer_unaryop, context, util.BadUnaryOperationMessage
  533. )
  534. return InferenceErrorInfo(node=self, context=context)
  535. nodes.UnaryOp._infer_unaryop = _infer_unaryop
  536. nodes.UnaryOp._infer = infer_unaryop
  537. def _is_not_implemented(const) -> bool:
  538. """Check if the given const node is NotImplemented."""
  539. return isinstance(const, nodes.Const) and const.value is NotImplemented
  540. def _infer_old_style_string_formatting(
  541. instance: nodes.Const, other: nodes.NodeNG, context: InferenceContext
  542. ) -> tuple[util.UninferableBase | nodes.Const]:
  543. """Infer the result of '"string" % ...'.
  544. TODO: Instead of returning Uninferable we should rely
  545. on the call to '%' to see if the result is actually uninferable.
  546. """
  547. if isinstance(other, nodes.Tuple):
  548. if util.Uninferable in other.elts:
  549. return (util.Uninferable,)
  550. inferred_positional = [helpers.safe_infer(i, context) for i in other.elts]
  551. if all(isinstance(i, nodes.Const) for i in inferred_positional):
  552. values = tuple(i.value for i in inferred_positional)
  553. else:
  554. values = None
  555. elif isinstance(other, nodes.Dict):
  556. values: dict[Any, Any] = {}
  557. for pair in other.items:
  558. key = helpers.safe_infer(pair[0], context)
  559. if not isinstance(key, nodes.Const):
  560. return (util.Uninferable,)
  561. value = helpers.safe_infer(pair[1], context)
  562. if not isinstance(value, nodes.Const):
  563. return (util.Uninferable,)
  564. values[key.value] = value.value
  565. elif isinstance(other, nodes.Const):
  566. values = other.value
  567. else:
  568. return (util.Uninferable,)
  569. try:
  570. return (nodes.const_factory(instance.value % values),)
  571. except (TypeError, KeyError, ValueError):
  572. return (util.Uninferable,)
  573. def _invoke_binop_inference(
  574. instance: InferenceResult,
  575. opnode: nodes.AugAssign | nodes.BinOp,
  576. op: str,
  577. other: InferenceResult,
  578. context: InferenceContext,
  579. method_name: str,
  580. ) -> Generator[InferenceResult, None, None]:
  581. """Invoke binary operation inference on the given instance."""
  582. methods = dunder_lookup.lookup(instance, method_name)
  583. context = bind_context_to_node(context, instance)
  584. method = methods[0]
  585. context.callcontext.callee = method
  586. if (
  587. isinstance(instance, nodes.Const)
  588. and isinstance(instance.value, str)
  589. and op == "%"
  590. ):
  591. return iter(_infer_old_style_string_formatting(instance, other, context))
  592. try:
  593. inferred = next(method.infer(context=context))
  594. except StopIteration as e:
  595. raise InferenceError(node=method, context=context) from e
  596. if isinstance(inferred, util.UninferableBase):
  597. raise InferenceError
  598. if not isinstance(
  599. instance, (nodes.Const, nodes.Tuple, nodes.List, nodes.ClassDef, bases.Instance)
  600. ):
  601. raise InferenceError # pragma: no cover # Used as a failsafe
  602. return instance.infer_binary_op(opnode, op, other, context, inferred)
  603. def _aug_op(
  604. instance: InferenceResult,
  605. opnode: nodes.AugAssign,
  606. op: str,
  607. other: InferenceResult,
  608. context: InferenceContext,
  609. reverse: bool = False,
  610. ) -> functools.partial[Generator[InferenceResult, None, None]]:
  611. """Get an inference callable for an augmented binary operation."""
  612. method_name = protocols.AUGMENTED_OP_METHOD[op]
  613. return functools.partial(
  614. _invoke_binop_inference,
  615. instance=instance,
  616. op=op,
  617. opnode=opnode,
  618. other=other,
  619. context=context,
  620. method_name=method_name,
  621. )
  622. def _bin_op(
  623. instance: InferenceResult,
  624. opnode: nodes.AugAssign | nodes.BinOp,
  625. op: str,
  626. other: InferenceResult,
  627. context: InferenceContext,
  628. reverse: bool = False,
  629. ) -> functools.partial[Generator[InferenceResult, None, None]]:
  630. """Get an inference callable for a normal binary operation.
  631. If *reverse* is True, then the reflected method will be used instead.
  632. """
  633. if reverse:
  634. method_name = protocols.REFLECTED_BIN_OP_METHOD[op]
  635. else:
  636. method_name = protocols.BIN_OP_METHOD[op]
  637. return functools.partial(
  638. _invoke_binop_inference,
  639. instance=instance,
  640. op=op,
  641. opnode=opnode,
  642. other=other,
  643. context=context,
  644. method_name=method_name,
  645. )
  646. def _bin_op_or_union_type(
  647. left: bases.UnionType | nodes.ClassDef | nodes.Const,
  648. right: bases.UnionType | nodes.ClassDef | nodes.Const,
  649. ) -> Generator[InferenceResult, None, None]:
  650. """Create a new UnionType instance for binary or, e.g. int | str."""
  651. yield bases.UnionType(left, right)
  652. def _get_binop_contexts(context, left, right):
  653. """Get contexts for binary operations.
  654. This will return two inference contexts, the first one
  655. for x.__op__(y), the other one for y.__rop__(x), where
  656. only the arguments are inversed.
  657. """
  658. # The order is important, since the first one should be
  659. # left.__op__(right).
  660. for arg in (right, left):
  661. new_context = context.clone()
  662. new_context.callcontext = CallContext(args=[arg])
  663. new_context.boundnode = None
  664. yield new_context
  665. def _same_type(type1, type2) -> bool:
  666. """Check if type1 is the same as type2."""
  667. return type1.qname() == type2.qname()
  668. def _get_binop_flow(
  669. left: InferenceResult,
  670. left_type: InferenceResult | None,
  671. binary_opnode: nodes.AugAssign | nodes.BinOp,
  672. right: InferenceResult,
  673. right_type: InferenceResult | None,
  674. context: InferenceContext,
  675. reverse_context: InferenceContext,
  676. ) -> list[functools.partial[Generator[InferenceResult, None, None]]]:
  677. """Get the flow for binary operations.
  678. The rules are a bit messy:
  679. * if left and right have the same type, then only one
  680. method will be called, left.__op__(right)
  681. * if left and right are unrelated typewise, then first
  682. left.__op__(right) is tried and if this does not exist
  683. or returns NotImplemented, then right.__rop__(left) is tried.
  684. * if left is a subtype of right, then only left.__op__(right)
  685. is tried.
  686. * if left is a supertype of right, then right.__rop__(left)
  687. is first tried and then left.__op__(right)
  688. """
  689. op = binary_opnode.op
  690. if _same_type(left_type, right_type):
  691. methods = [_bin_op(left, binary_opnode, op, right, context)]
  692. elif helpers.is_subtype(left_type, right_type):
  693. methods = [_bin_op(left, binary_opnode, op, right, context)]
  694. elif helpers.is_supertype(left_type, right_type):
  695. methods = [
  696. _bin_op(right, binary_opnode, op, left, reverse_context, reverse=True),
  697. _bin_op(left, binary_opnode, op, right, context),
  698. ]
  699. else:
  700. methods = [
  701. _bin_op(left, binary_opnode, op, right, context),
  702. _bin_op(right, binary_opnode, op, left, reverse_context, reverse=True),
  703. ]
  704. if (
  705. PY310_PLUS
  706. and op == "|"
  707. and (
  708. isinstance(left, (bases.UnionType, nodes.ClassDef))
  709. or isinstance(left, nodes.Const)
  710. and left.value is None
  711. )
  712. and (
  713. isinstance(right, (bases.UnionType, nodes.ClassDef))
  714. or isinstance(right, nodes.Const)
  715. and right.value is None
  716. )
  717. ):
  718. methods.extend([functools.partial(_bin_op_or_union_type, left, right)])
  719. return methods
  720. def _get_aug_flow(
  721. left: InferenceResult,
  722. left_type: InferenceResult | None,
  723. aug_opnode: nodes.AugAssign,
  724. right: InferenceResult,
  725. right_type: InferenceResult | None,
  726. context: InferenceContext,
  727. reverse_context: InferenceContext,
  728. ) -> list[functools.partial[Generator[InferenceResult, None, None]]]:
  729. """Get the flow for augmented binary operations.
  730. The rules are a bit messy:
  731. * if left and right have the same type, then left.__augop__(right)
  732. is first tried and then left.__op__(right).
  733. * if left and right are unrelated typewise, then
  734. left.__augop__(right) is tried, then left.__op__(right)
  735. is tried and then right.__rop__(left) is tried.
  736. * if left is a subtype of right, then left.__augop__(right)
  737. is tried and then left.__op__(right).
  738. * if left is a supertype of right, then left.__augop__(right)
  739. is tried, then right.__rop__(left) and then
  740. left.__op__(right)
  741. """
  742. bin_op = aug_opnode.op.strip("=")
  743. aug_op = aug_opnode.op
  744. if _same_type(left_type, right_type):
  745. methods = [
  746. _aug_op(left, aug_opnode, aug_op, right, context),
  747. _bin_op(left, aug_opnode, bin_op, right, context),
  748. ]
  749. elif helpers.is_subtype(left_type, right_type):
  750. methods = [
  751. _aug_op(left, aug_opnode, aug_op, right, context),
  752. _bin_op(left, aug_opnode, bin_op, right, context),
  753. ]
  754. elif helpers.is_supertype(left_type, right_type):
  755. methods = [
  756. _aug_op(left, aug_opnode, aug_op, right, context),
  757. _bin_op(right, aug_opnode, bin_op, left, reverse_context, reverse=True),
  758. _bin_op(left, aug_opnode, bin_op, right, context),
  759. ]
  760. else:
  761. methods = [
  762. _aug_op(left, aug_opnode, aug_op, right, context),
  763. _bin_op(left, aug_opnode, bin_op, right, context),
  764. _bin_op(right, aug_opnode, bin_op, left, reverse_context, reverse=True),
  765. ]
  766. return methods
  767. def _infer_binary_operation(
  768. left: InferenceResult,
  769. right: InferenceResult,
  770. binary_opnode: nodes.AugAssign | nodes.BinOp,
  771. context: InferenceContext,
  772. flow_factory: GetFlowFactory,
  773. ) -> Generator[InferenceResult | util.BadBinaryOperationMessage, None, None]:
  774. """Infer a binary operation between a left operand and a right operand.
  775. This is used by both normal binary operations and augmented binary
  776. operations, the only difference is the flow factory used.
  777. """
  778. context, reverse_context = _get_binop_contexts(context, left, right)
  779. left_type = helpers.object_type(left)
  780. right_type = helpers.object_type(right)
  781. methods = flow_factory(
  782. left, left_type, binary_opnode, right, right_type, context, reverse_context
  783. )
  784. for method in methods:
  785. try:
  786. results = list(method())
  787. except AttributeError:
  788. continue
  789. except AttributeInferenceError:
  790. continue
  791. except InferenceError:
  792. yield util.Uninferable
  793. return
  794. else:
  795. if any(isinstance(result, util.UninferableBase) for result in results):
  796. yield util.Uninferable
  797. return
  798. if all(map(_is_not_implemented, results)):
  799. continue
  800. not_implemented = sum(
  801. 1 for result in results if _is_not_implemented(result)
  802. )
  803. if not_implemented and not_implemented != len(results):
  804. # Can't infer yet what this is.
  805. yield util.Uninferable
  806. return
  807. yield from results
  808. return
  809. # The operation doesn't seem to be supported so let the caller know about it
  810. yield util.BadBinaryOperationMessage(left_type, binary_opnode.op, right_type)
  811. def _infer_binop(
  812. self: nodes.BinOp, context: InferenceContext | None = None
  813. ) -> Generator[InferenceResult | util.BadBinaryOperationMessage, None, None]:
  814. """Binary operation inference logic."""
  815. left = self.left
  816. right = self.right
  817. # we use two separate contexts for evaluating lhs and rhs because
  818. # 1. evaluating lhs may leave some undesired entries in context.path
  819. # which may not let us infer right value of rhs
  820. context = context or InferenceContext()
  821. lhs_context = copy_context(context)
  822. rhs_context = copy_context(context)
  823. lhs_iter = left.infer(context=lhs_context)
  824. rhs_iter = right.infer(context=rhs_context)
  825. for lhs, rhs in itertools.product(lhs_iter, rhs_iter):
  826. if any(isinstance(value, util.UninferableBase) for value in (rhs, lhs)):
  827. # Don't know how to process this.
  828. yield util.Uninferable
  829. return
  830. try:
  831. yield from _infer_binary_operation(lhs, rhs, self, context, _get_binop_flow)
  832. except _NonDeducibleTypeHierarchy:
  833. yield util.Uninferable
  834. @decorators.yes_if_nothing_inferred
  835. @decorators.path_wrapper
  836. def infer_binop(
  837. self: nodes.BinOp, context: InferenceContext | None = None, **kwargs: Any
  838. ) -> Generator[InferenceResult, None, None]:
  839. return _filter_operation_errors(
  840. self, _infer_binop, context, util.BadBinaryOperationMessage
  841. )
  842. nodes.BinOp._infer_binop = _infer_binop
  843. nodes.BinOp._infer = infer_binop
  844. COMPARE_OPS: dict[str, Callable[[Any, Any], bool]] = {
  845. "==": operator.eq,
  846. "!=": operator.ne,
  847. "<": operator.lt,
  848. "<=": operator.le,
  849. ">": operator.gt,
  850. ">=": operator.ge,
  851. "in": lambda a, b: a in b,
  852. "not in": lambda a, b: a not in b,
  853. }
  854. UNINFERABLE_OPS = {
  855. "is",
  856. "is not",
  857. }
  858. def _to_literal(node: nodes.NodeNG) -> Any:
  859. # Can raise SyntaxError or ValueError from ast.literal_eval
  860. # Can raise AttributeError from node.as_string() as not all nodes have a visitor
  861. # Is this the stupidest idea or the simplest idea?
  862. return ast.literal_eval(node.as_string())
  863. def _do_compare(
  864. left_iter: Iterable[nodes.NodeNG], op: str, right_iter: Iterable[nodes.NodeNG]
  865. ) -> bool | util.UninferableBase:
  866. """
  867. If all possible combinations are either True or False, return that:
  868. >>> _do_compare([1, 2], '<=', [3, 4])
  869. True
  870. >>> _do_compare([1, 2], '==', [3, 4])
  871. False
  872. If any item is uninferable, or if some combinations are True and some
  873. are False, return Uninferable:
  874. >>> _do_compare([1, 3], '<=', [2, 4])
  875. util.Uninferable
  876. """
  877. retval: bool | None = None
  878. if op in UNINFERABLE_OPS:
  879. return util.Uninferable
  880. op_func = COMPARE_OPS[op]
  881. for left, right in itertools.product(left_iter, right_iter):
  882. if isinstance(left, util.UninferableBase) or isinstance(
  883. right, util.UninferableBase
  884. ):
  885. return util.Uninferable
  886. try:
  887. left, right = _to_literal(left), _to_literal(right)
  888. except (SyntaxError, ValueError, AttributeError):
  889. return util.Uninferable
  890. try:
  891. expr = op_func(left, right)
  892. except TypeError as exc:
  893. raise AstroidTypeError from exc
  894. if retval is None:
  895. retval = expr
  896. elif retval != expr:
  897. return util.Uninferable
  898. # (or both, but "True | False" is basically the same)
  899. assert retval is not None
  900. return retval # it was all the same value
  901. def _infer_compare(
  902. self: nodes.Compare, context: InferenceContext | None = None, **kwargs: Any
  903. ) -> Generator[nodes.Const | util.UninferableBase, None, None]:
  904. """Chained comparison inference logic."""
  905. retval: bool | util.UninferableBase = True
  906. ops = self.ops
  907. left_node = self.left
  908. lhs = list(left_node.infer(context=context))
  909. # should we break early if first element is uninferable?
  910. for op, right_node in ops:
  911. # eagerly evaluate rhs so that values can be re-used as lhs
  912. rhs = list(right_node.infer(context=context))
  913. try:
  914. retval = _do_compare(lhs, op, rhs)
  915. except AstroidTypeError:
  916. retval = util.Uninferable
  917. break
  918. if retval is not True:
  919. break # short-circuit
  920. lhs = rhs # continue
  921. if retval is util.Uninferable:
  922. yield retval # type: ignore[misc]
  923. else:
  924. yield nodes.Const(retval)
  925. nodes.Compare._infer = _infer_compare # type: ignore[assignment]
  926. def _infer_augassign(
  927. self: nodes.AugAssign, context: InferenceContext | None = None
  928. ) -> Generator[InferenceResult | util.BadBinaryOperationMessage, None, None]:
  929. """Inference logic for augmented binary operations."""
  930. context = context or InferenceContext()
  931. rhs_context = context.clone()
  932. lhs_iter = self.target.infer_lhs(context=context)
  933. rhs_iter = self.value.infer(context=rhs_context)
  934. for lhs, rhs in itertools.product(lhs_iter, rhs_iter):
  935. if any(isinstance(value, util.UninferableBase) for value in (rhs, lhs)):
  936. # Don't know how to process this.
  937. yield util.Uninferable
  938. return
  939. try:
  940. yield from _infer_binary_operation(
  941. left=lhs,
  942. right=rhs,
  943. binary_opnode=self,
  944. context=context,
  945. flow_factory=_get_aug_flow,
  946. )
  947. except _NonDeducibleTypeHierarchy:
  948. yield util.Uninferable
  949. @decorators.raise_if_nothing_inferred
  950. @decorators.path_wrapper
  951. def infer_augassign(
  952. self: nodes.AugAssign, context: InferenceContext | None = None, **kwargs: Any
  953. ) -> Generator[InferenceResult, None, None]:
  954. return _filter_operation_errors(
  955. self, _infer_augassign, context, util.BadBinaryOperationMessage
  956. )
  957. nodes.AugAssign._infer_augassign = _infer_augassign
  958. nodes.AugAssign._infer = infer_augassign
  959. # End of binary operation inference.
  960. @decorators.raise_if_nothing_inferred
  961. def infer_arguments(
  962. self: nodes.Arguments, context: InferenceContext | None = None, **kwargs: Any
  963. ) -> Generator[InferenceResult, None, None]:
  964. if context is None or context.lookupname is None:
  965. raise InferenceError(node=self, context=context)
  966. return protocols._arguments_infer_argname(self, context.lookupname, context)
  967. nodes.Arguments._infer = infer_arguments # type: ignore[assignment]
  968. @decorators.raise_if_nothing_inferred
  969. @decorators.path_wrapper
  970. def infer_assign(
  971. self: nodes.AssignName | nodes.AssignAttr,
  972. context: InferenceContext | None = None,
  973. **kwargs: Any,
  974. ) -> Generator[InferenceResult, None, None]:
  975. """Infer a AssignName/AssignAttr: need to inspect the RHS part of the
  976. assign node.
  977. """
  978. if isinstance(self.parent, nodes.AugAssign):
  979. return self.parent.infer(context)
  980. stmts = list(self.assigned_stmts(context=context))
  981. return bases._infer_stmts(stmts, context)
  982. nodes.AssignName._infer = infer_assign
  983. nodes.AssignAttr._infer = infer_assign
  984. @decorators.raise_if_nothing_inferred
  985. @decorators.path_wrapper
  986. def infer_empty_node(
  987. self: nodes.EmptyNode, context: InferenceContext | None = None, **kwargs: Any
  988. ) -> Generator[InferenceResult, None, None]:
  989. if not self.has_underlying_object():
  990. yield util.Uninferable
  991. else:
  992. try:
  993. yield from AstroidManager().infer_ast_from_something(
  994. self.object, context=context
  995. )
  996. except AstroidError:
  997. yield util.Uninferable
  998. nodes.EmptyNode._infer = infer_empty_node # type: ignore[assignment]
  999. def _populate_context_lookup(call: nodes.Call, context: InferenceContext | None):
  1000. # Allows context to be saved for later
  1001. # for inference inside a function
  1002. context_lookup: dict[InferenceResult, InferenceContext] = {}
  1003. if context is None:
  1004. return context_lookup
  1005. for arg in call.args:
  1006. if isinstance(arg, nodes.Starred):
  1007. context_lookup[arg.value] = context
  1008. else:
  1009. context_lookup[arg] = context
  1010. keywords = call.keywords if call.keywords is not None else []
  1011. for keyword in keywords:
  1012. context_lookup[keyword.value] = context
  1013. return context_lookup
  1014. @decorators.raise_if_nothing_inferred
  1015. def infer_ifexp(
  1016. self: nodes.IfExp, context: InferenceContext | None = None, **kwargs: Any
  1017. ) -> Generator[InferenceResult, None, None]:
  1018. """Support IfExp inference.
  1019. If we can't infer the truthiness of the condition, we default
  1020. to inferring both branches. Otherwise, we infer either branch
  1021. depending on the condition.
  1022. """
  1023. both_branches = False
  1024. # We use two separate contexts for evaluating lhs and rhs because
  1025. # evaluating lhs may leave some undesired entries in context.path
  1026. # which may not let us infer right value of rhs.
  1027. context = context or InferenceContext()
  1028. lhs_context = copy_context(context)
  1029. rhs_context = copy_context(context)
  1030. try:
  1031. test = next(self.test.infer(context=context.clone()))
  1032. except (InferenceError, StopIteration):
  1033. both_branches = True
  1034. else:
  1035. if not isinstance(test, util.UninferableBase):
  1036. if test.bool_value():
  1037. yield from self.body.infer(context=lhs_context)
  1038. else:
  1039. yield from self.orelse.infer(context=rhs_context)
  1040. else:
  1041. both_branches = True
  1042. if both_branches:
  1043. yield from self.body.infer(context=lhs_context)
  1044. yield from self.orelse.infer(context=rhs_context)
  1045. nodes.IfExp._infer = infer_ifexp # type: ignore[assignment]
  1046. def infer_functiondef(
  1047. self: _FunctionDefT, context: InferenceContext | None = None, **kwargs: Any
  1048. ) -> Generator[Property | _FunctionDefT, None, InferenceErrorInfo]:
  1049. if not self.decorators or not bases._is_property(self):
  1050. yield self
  1051. return InferenceErrorInfo(node=self, context=context)
  1052. # When inferring a property, we instantiate a new `objects.Property` object,
  1053. # which in turn, because it inherits from `FunctionDef`, sets itself in the locals
  1054. # of the wrapping frame. This means that every time we infer a property, the locals
  1055. # are mutated with a new instance of the property. To avoid this, we detect this
  1056. # scenario and avoid passing the `parent` argument to the constructor.
  1057. parent_frame = self.parent.frame(future=True)
  1058. property_already_in_parent_locals = self.name in parent_frame.locals and any(
  1059. isinstance(val, objects.Property) for val in parent_frame.locals[self.name]
  1060. )
  1061. # We also don't want to pass parent if the definition is within a Try node
  1062. if isinstance(self.parent, (nodes.TryExcept, nodes.TryFinally, nodes.If)):
  1063. property_already_in_parent_locals = True
  1064. prop_func = objects.Property(
  1065. function=self,
  1066. name=self.name,
  1067. lineno=self.lineno,
  1068. parent=self.parent if not property_already_in_parent_locals else None,
  1069. col_offset=self.col_offset,
  1070. )
  1071. if property_already_in_parent_locals:
  1072. prop_func.parent = self.parent
  1073. prop_func.postinit(body=[], args=self.args, doc_node=self.doc_node)
  1074. yield prop_func
  1075. return InferenceErrorInfo(node=self, context=context)
  1076. nodes.FunctionDef._infer = infer_functiondef