objectmodel.py 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  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. """
  5. Data object model, as per https://docs.python.org/3/reference/datamodel.html.
  6. This module describes, at least partially, a data object model for some
  7. of astroid's nodes. The model contains special attributes that nodes such
  8. as functions, classes, modules etc have, such as __doc__, __class__,
  9. __module__ etc, being used when doing attribute lookups over nodes.
  10. For instance, inferring `obj.__class__` will first trigger an inference
  11. of the `obj` variable. If it was successfully inferred, then an attribute
  12. `__class__ will be looked for in the inferred object. This is the part
  13. where the data model occurs. The model is attached to those nodes
  14. and the lookup mechanism will try to see if attributes such as
  15. `__class__` are defined by the model or not. If they are defined,
  16. the model will be requested to return the corresponding value of that
  17. attribute. Thus the model can be viewed as a special part of the lookup
  18. mechanism.
  19. """
  20. from __future__ import annotations
  21. import itertools
  22. import os
  23. import pprint
  24. import types
  25. from collections.abc import Iterator
  26. from functools import lru_cache
  27. from typing import TYPE_CHECKING, Any, Literal
  28. import astroid
  29. from astroid import bases, nodes, util
  30. from astroid.context import InferenceContext, copy_context
  31. from astroid.exceptions import AttributeInferenceError, InferenceError, NoDefault
  32. from astroid.manager import AstroidManager
  33. from astroid.nodes import node_classes
  34. from astroid.typing import InferenceResult, SuccessfulInferenceResult
  35. if TYPE_CHECKING:
  36. from astroid.objects import Property
  37. IMPL_PREFIX = "attr_"
  38. LEN_OF_IMPL_PREFIX = len(IMPL_PREFIX)
  39. def _dunder_dict(instance, attributes):
  40. obj = node_classes.Dict(
  41. parent=instance,
  42. lineno=instance.lineno,
  43. col_offset=instance.col_offset,
  44. end_lineno=instance.end_lineno,
  45. end_col_offset=instance.end_col_offset,
  46. )
  47. # Convert the keys to node strings
  48. keys = [
  49. node_classes.Const(value=value, parent=obj) for value in list(attributes.keys())
  50. ]
  51. # The original attribute has a list of elements for each key,
  52. # but that is not useful for retrieving the special attribute's value.
  53. # In this case, we're picking the last value from each list.
  54. values = [elem[-1] for elem in attributes.values()]
  55. obj.postinit(list(zip(keys, values)))
  56. return obj
  57. def _get_bound_node(model: ObjectModel) -> Any:
  58. # TODO: Use isinstance instead of try ... except after _instance has typing
  59. try:
  60. return model._instance._proxied
  61. except AttributeError:
  62. return model._instance
  63. class ObjectModel:
  64. def __init__(self):
  65. self._instance = None
  66. def __repr__(self):
  67. result = []
  68. cname = type(self).__name__
  69. string = "%(cname)s(%(fields)s)"
  70. alignment = len(cname) + 1
  71. for field in sorted(self.attributes()):
  72. width = 80 - len(field) - alignment
  73. lines = pprint.pformat(field, indent=2, width=width).splitlines(True)
  74. inner = [lines[0]]
  75. for line in lines[1:]:
  76. inner.append(" " * alignment + line)
  77. result.append(field)
  78. return string % {
  79. "cname": cname,
  80. "fields": (",\n" + " " * alignment).join(result),
  81. }
  82. def __call__(self, instance):
  83. self._instance = instance
  84. return self
  85. def __get__(self, instance, cls=None):
  86. # ObjectModel needs to be a descriptor so that just doing
  87. # `special_attributes = SomeObjectModel` should be enough in the body of a node.
  88. # But at the same time, node.special_attributes should return an object
  89. # which can be used for manipulating the special attributes. That's the reason
  90. # we pass the instance through which it got accessed to ObjectModel.__call__,
  91. # returning itself afterwards, so we can still have access to the
  92. # underlying data model and to the instance for which it got accessed.
  93. return self(instance)
  94. def __contains__(self, name) -> bool:
  95. return name in self.attributes()
  96. @lru_cache # noqa
  97. def attributes(self) -> list[str]:
  98. """Get the attributes which are exported by this object model."""
  99. return [o[LEN_OF_IMPL_PREFIX:] for o in dir(self) if o.startswith(IMPL_PREFIX)]
  100. def lookup(self, name):
  101. """Look up the given *name* in the current model.
  102. It should return an AST or an interpreter object,
  103. but if the name is not found, then an AttributeInferenceError will be raised.
  104. """
  105. if name in self.attributes():
  106. return getattr(self, IMPL_PREFIX + name)
  107. raise AttributeInferenceError(target=self._instance, attribute=name)
  108. @property
  109. def attr___new__(self) -> bases.BoundMethod:
  110. """Calling cls.__new__(type) on an object returns an instance of 'type'."""
  111. from astroid import builder # pylint: disable=import-outside-toplevel
  112. node: nodes.FunctionDef = builder.extract_node(
  113. """def __new__(self, cls): return cls()"""
  114. )
  115. # We set the parent as being the ClassDef of 'object' as that
  116. # triggers correct inference as a call to __new__ in bases.py
  117. node.parent = AstroidManager().builtins_module["object"]
  118. return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
  119. @property
  120. def attr___init__(self) -> bases.BoundMethod:
  121. """Calling cls.__init__() normally returns None."""
  122. from astroid import builder # pylint: disable=import-outside-toplevel
  123. # The *args and **kwargs are necessary not to trigger warnings about missing
  124. # or extra parameters for '__init__' methods we don't infer correctly.
  125. # This BoundMethod is the fallback value for those.
  126. node: nodes.FunctionDef = builder.extract_node(
  127. """def __init__(self, *args, **kwargs): return None"""
  128. )
  129. # We set the parent as being the ClassDef of 'object' as that
  130. # is where this method originally comes from
  131. node.parent = AstroidManager().builtins_module["object"]
  132. return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
  133. class ModuleModel(ObjectModel):
  134. def _builtins(self):
  135. builtins_ast_module = AstroidManager().builtins_module
  136. return builtins_ast_module.special_attributes.lookup("__dict__")
  137. @property
  138. def attr_builtins(self):
  139. return self._builtins()
  140. @property
  141. def attr___path__(self):
  142. if not self._instance.package:
  143. raise AttributeInferenceError(target=self._instance, attribute="__path__")
  144. path_objs = [
  145. node_classes.Const(
  146. value=(
  147. path if not path.endswith("__init__.py") else os.path.dirname(path)
  148. ),
  149. parent=self._instance,
  150. )
  151. for path in self._instance.path
  152. ]
  153. container = node_classes.List(parent=self._instance)
  154. container.postinit(path_objs)
  155. return container
  156. @property
  157. def attr___name__(self):
  158. return node_classes.Const(value=self._instance.name, parent=self._instance)
  159. @property
  160. def attr___doc__(self):
  161. return node_classes.Const(
  162. value=getattr(self._instance.doc_node, "value", None),
  163. parent=self._instance,
  164. )
  165. @property
  166. def attr___file__(self):
  167. return node_classes.Const(value=self._instance.file, parent=self._instance)
  168. @property
  169. def attr___dict__(self):
  170. return _dunder_dict(self._instance, self._instance.globals)
  171. @property
  172. def attr___package__(self):
  173. if not self._instance.package:
  174. value = ""
  175. else:
  176. value = self._instance.name
  177. return node_classes.Const(value=value, parent=self._instance)
  178. # These are related to the Python 3 implementation of the
  179. # import system,
  180. # https://docs.python.org/3/reference/import.html#import-related-module-attributes
  181. @property
  182. def attr___spec__(self):
  183. # No handling for now.
  184. return node_classes.Unknown(parent=self._instance)
  185. @property
  186. def attr___loader__(self):
  187. # No handling for now.
  188. return node_classes.Unknown(parent=self._instance)
  189. @property
  190. def attr___cached__(self):
  191. # No handling for now.
  192. return node_classes.Unknown(parent=self._instance)
  193. class FunctionModel(ObjectModel):
  194. @property
  195. def attr___name__(self):
  196. return node_classes.Const(value=self._instance.name, parent=self._instance)
  197. @property
  198. def attr___doc__(self):
  199. return node_classes.Const(
  200. value=getattr(self._instance.doc_node, "value", None),
  201. parent=self._instance,
  202. )
  203. @property
  204. def attr___qualname__(self):
  205. return node_classes.Const(value=self._instance.qname(), parent=self._instance)
  206. @property
  207. def attr___defaults__(self):
  208. func = self._instance
  209. if not func.args.defaults:
  210. return node_classes.Const(value=None, parent=func)
  211. defaults_obj = node_classes.Tuple(parent=func)
  212. defaults_obj.postinit(func.args.defaults)
  213. return defaults_obj
  214. @property
  215. def attr___annotations__(self):
  216. obj = node_classes.Dict(
  217. parent=self._instance,
  218. lineno=self._instance.lineno,
  219. col_offset=self._instance.col_offset,
  220. end_lineno=self._instance.end_lineno,
  221. end_col_offset=self._instance.end_col_offset,
  222. )
  223. if not self._instance.returns:
  224. returns = None
  225. else:
  226. returns = self._instance.returns
  227. args = self._instance.args
  228. pair_annotations = itertools.chain(
  229. zip(args.args or [], args.annotations),
  230. zip(args.kwonlyargs, args.kwonlyargs_annotations),
  231. zip(args.posonlyargs or [], args.posonlyargs_annotations),
  232. )
  233. annotations = {
  234. arg.name: annotation for (arg, annotation) in pair_annotations if annotation
  235. }
  236. if args.varargannotation:
  237. annotations[args.vararg] = args.varargannotation
  238. if args.kwargannotation:
  239. annotations[args.kwarg] = args.kwargannotation
  240. if returns:
  241. annotations["return"] = returns
  242. items = [
  243. (node_classes.Const(key, parent=obj), value)
  244. for (key, value) in annotations.items()
  245. ]
  246. obj.postinit(items)
  247. return obj
  248. @property
  249. def attr___dict__(self):
  250. return node_classes.Dict(
  251. parent=self._instance,
  252. lineno=self._instance.lineno,
  253. col_offset=self._instance.col_offset,
  254. end_lineno=self._instance.end_lineno,
  255. end_col_offset=self._instance.end_col_offset,
  256. )
  257. attr___globals__ = attr___dict__
  258. @property
  259. def attr___kwdefaults__(self):
  260. def _default_args(args, parent):
  261. for arg in args.kwonlyargs:
  262. try:
  263. default = args.default_value(arg.name)
  264. except NoDefault:
  265. continue
  266. name = node_classes.Const(arg.name, parent=parent)
  267. yield name, default
  268. args = self._instance.args
  269. obj = node_classes.Dict(
  270. parent=self._instance,
  271. lineno=self._instance.lineno,
  272. col_offset=self._instance.col_offset,
  273. end_lineno=self._instance.end_lineno,
  274. end_col_offset=self._instance.end_col_offset,
  275. )
  276. defaults = dict(_default_args(args, obj))
  277. obj.postinit(list(defaults.items()))
  278. return obj
  279. @property
  280. def attr___module__(self):
  281. return node_classes.Const(self._instance.root().qname())
  282. @property
  283. def attr___get__(self):
  284. func = self._instance
  285. class DescriptorBoundMethod(bases.BoundMethod):
  286. """Bound method which knows how to understand calling descriptor
  287. binding.
  288. """
  289. def implicit_parameters(self) -> Literal[0]:
  290. # Different than BoundMethod since the signature
  291. # is different.
  292. return 0
  293. def infer_call_result(
  294. self,
  295. caller: SuccessfulInferenceResult | None,
  296. context: InferenceContext | None = None,
  297. ) -> Iterator[bases.BoundMethod]:
  298. if len(caller.args) > 2 or len(caller.args) < 1:
  299. raise InferenceError(
  300. "Invalid arguments for descriptor binding",
  301. target=self,
  302. context=context,
  303. )
  304. context = copy_context(context)
  305. try:
  306. cls = next(caller.args[0].infer(context=context))
  307. except StopIteration as e:
  308. raise InferenceError(context=context, node=caller.args[0]) from e
  309. if isinstance(cls, util.UninferableBase):
  310. raise InferenceError(
  311. "Invalid class inferred", target=self, context=context
  312. )
  313. # For some reason func is a Node that the below
  314. # code is not expecting
  315. if isinstance(func, bases.BoundMethod):
  316. yield func
  317. return
  318. # Rebuild the original value, but with the parent set as the
  319. # class where it will be bound.
  320. new_func = func.__class__(
  321. name=func.name,
  322. lineno=func.lineno,
  323. col_offset=func.col_offset,
  324. parent=func.parent,
  325. end_lineno=func.end_lineno,
  326. end_col_offset=func.end_col_offset,
  327. )
  328. # pylint: disable=no-member
  329. new_func.postinit(
  330. func.args,
  331. func.body,
  332. func.decorators,
  333. func.returns,
  334. doc_node=func.doc_node,
  335. )
  336. # Build a proper bound method that points to our newly built function.
  337. proxy = bases.UnboundMethod(new_func)
  338. yield bases.BoundMethod(proxy=proxy, bound=cls)
  339. @property
  340. def args(self):
  341. """Overwrite the underlying args to match those of the underlying func.
  342. Usually the underlying *func* is a function/method, as in:
  343. def test(self):
  344. pass
  345. This has only the *self* parameter but when we access test.__get__
  346. we get a new object which has two parameters, *self* and *type*.
  347. """
  348. nonlocal func
  349. arguments = nodes.Arguments(
  350. parent=func.args.parent, vararg=None, kwarg=None
  351. )
  352. positional_or_keyword_params = func.args.args.copy()
  353. positional_or_keyword_params.append(
  354. nodes.AssignName(
  355. name="type",
  356. lineno=0,
  357. col_offset=0,
  358. parent=arguments,
  359. end_lineno=None,
  360. end_col_offset=None,
  361. )
  362. )
  363. positional_only_params = func.args.posonlyargs.copy()
  364. arguments.postinit(
  365. args=positional_or_keyword_params,
  366. posonlyargs=positional_only_params,
  367. defaults=[],
  368. kwonlyargs=[],
  369. kw_defaults=[],
  370. annotations=[],
  371. kwonlyargs_annotations=[],
  372. posonlyargs_annotations=[],
  373. )
  374. return arguments
  375. return DescriptorBoundMethod(proxy=self._instance, bound=self._instance)
  376. # These are here just for completion.
  377. @property
  378. def attr___ne__(self):
  379. return node_classes.Unknown(parent=self._instance)
  380. attr___subclasshook__ = attr___ne__
  381. attr___str__ = attr___ne__
  382. attr___sizeof__ = attr___ne__
  383. attr___setattr___ = attr___ne__
  384. attr___repr__ = attr___ne__
  385. attr___reduce__ = attr___ne__
  386. attr___reduce_ex__ = attr___ne__
  387. attr___lt__ = attr___ne__
  388. attr___eq__ = attr___ne__
  389. attr___gt__ = attr___ne__
  390. attr___format__ = attr___ne__
  391. attr___delattr___ = attr___ne__
  392. attr___getattribute__ = attr___ne__
  393. attr___hash__ = attr___ne__
  394. attr___dir__ = attr___ne__
  395. attr___call__ = attr___ne__
  396. attr___class__ = attr___ne__
  397. attr___closure__ = attr___ne__
  398. attr___code__ = attr___ne__
  399. class ClassModel(ObjectModel):
  400. def __init__(self):
  401. # Add a context so that inferences called from an instance don't recurse endlessly
  402. self.context = InferenceContext()
  403. super().__init__()
  404. @property
  405. def attr___annotations__(self) -> node_classes.Unknown:
  406. return node_classes.Unknown(parent=self._instance)
  407. @property
  408. def attr___module__(self):
  409. return node_classes.Const(self._instance.root().qname())
  410. @property
  411. def attr___name__(self):
  412. return node_classes.Const(self._instance.name)
  413. @property
  414. def attr___qualname__(self):
  415. return node_classes.Const(self._instance.qname())
  416. @property
  417. def attr___doc__(self):
  418. return node_classes.Const(getattr(self._instance.doc_node, "value", None))
  419. @property
  420. def attr___mro__(self):
  421. mro = self._instance.mro()
  422. obj = node_classes.Tuple(parent=self._instance)
  423. obj.postinit(mro)
  424. return obj
  425. @property
  426. def attr_mro(self):
  427. other_self = self
  428. # Cls.mro is a method and we need to return one in order to have a proper inference.
  429. # The method we're returning is capable of inferring the underlying MRO though.
  430. class MroBoundMethod(bases.BoundMethod):
  431. def infer_call_result(
  432. self,
  433. caller: SuccessfulInferenceResult | None,
  434. context: InferenceContext | None = None,
  435. ) -> Iterator[node_classes.Tuple]:
  436. yield other_self.attr___mro__
  437. implicit_metaclass = self._instance.implicit_metaclass()
  438. mro_method = implicit_metaclass.locals["mro"][0]
  439. return MroBoundMethod(proxy=mro_method, bound=implicit_metaclass)
  440. @property
  441. def attr___bases__(self):
  442. obj = node_classes.Tuple()
  443. context = InferenceContext()
  444. elts = list(self._instance._inferred_bases(context))
  445. obj.postinit(elts=elts)
  446. return obj
  447. @property
  448. def attr___class__(self):
  449. # pylint: disable=import-outside-toplevel; circular import
  450. from astroid import helpers
  451. return helpers.object_type(self._instance)
  452. @property
  453. def attr___subclasses__(self):
  454. """Get the subclasses of the underlying class.
  455. This looks only in the current module for retrieving the subclasses,
  456. thus it might miss a couple of them.
  457. """
  458. qname = self._instance.qname()
  459. root = self._instance.root()
  460. classes = [
  461. cls
  462. for cls in root.nodes_of_class(nodes.ClassDef)
  463. if cls != self._instance and cls.is_subtype_of(qname, context=self.context)
  464. ]
  465. obj = node_classes.List(parent=self._instance)
  466. obj.postinit(classes)
  467. class SubclassesBoundMethod(bases.BoundMethod):
  468. def infer_call_result(
  469. self,
  470. caller: SuccessfulInferenceResult | None,
  471. context: InferenceContext | None = None,
  472. ) -> Iterator[node_classes.List]:
  473. yield obj
  474. implicit_metaclass = self._instance.implicit_metaclass()
  475. subclasses_method = implicit_metaclass.locals["__subclasses__"][0]
  476. return SubclassesBoundMethod(proxy=subclasses_method, bound=implicit_metaclass)
  477. @property
  478. def attr___dict__(self):
  479. return node_classes.Dict(
  480. parent=self._instance,
  481. lineno=self._instance.lineno,
  482. col_offset=self._instance.col_offset,
  483. end_lineno=self._instance.end_lineno,
  484. end_col_offset=self._instance.end_col_offset,
  485. )
  486. @property
  487. def attr___call__(self):
  488. """Calling a class A() returns an instance of A."""
  489. return self._instance.instantiate_class()
  490. class SuperModel(ObjectModel):
  491. @property
  492. def attr___thisclass__(self):
  493. return self._instance.mro_pointer
  494. @property
  495. def attr___self_class__(self):
  496. return self._instance._self_class
  497. @property
  498. def attr___self__(self):
  499. return self._instance.type
  500. @property
  501. def attr___class__(self):
  502. return self._instance._proxied
  503. class UnboundMethodModel(ObjectModel):
  504. @property
  505. def attr___class__(self):
  506. # pylint: disable=import-outside-toplevel; circular import
  507. from astroid import helpers
  508. return helpers.object_type(self._instance)
  509. @property
  510. def attr___func__(self):
  511. return self._instance._proxied
  512. @property
  513. def attr___self__(self):
  514. return node_classes.Const(value=None, parent=self._instance)
  515. attr_im_func = attr___func__
  516. attr_im_class = attr___class__
  517. attr_im_self = attr___self__
  518. class ContextManagerModel(ObjectModel):
  519. """Model for context managers.
  520. Based on 3.3.9 of the Data Model documentation:
  521. https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
  522. """
  523. @property
  524. def attr___enter__(self) -> bases.BoundMethod:
  525. """Representation of the base implementation of __enter__.
  526. As per Python documentation:
  527. Enter the runtime context related to this object. The with statement
  528. will bind this method's return value to the target(s) specified in the
  529. as clause of the statement, if any.
  530. """
  531. from astroid import builder # pylint: disable=import-outside-toplevel
  532. node: nodes.FunctionDef = builder.extract_node("""def __enter__(self): ...""")
  533. # We set the parent as being the ClassDef of 'object' as that
  534. # is where this method originally comes from
  535. node.parent = AstroidManager().builtins_module["object"]
  536. return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
  537. @property
  538. def attr___exit__(self) -> bases.BoundMethod:
  539. """Representation of the base implementation of __exit__.
  540. As per Python documentation:
  541. Exit the runtime context related to this object. The parameters describe the
  542. exception that caused the context to be exited. If the context was exited
  543. without an exception, all three arguments will be None.
  544. """
  545. from astroid import builder # pylint: disable=import-outside-toplevel
  546. node: nodes.FunctionDef = builder.extract_node(
  547. """def __exit__(self, exc_type, exc_value, traceback): ..."""
  548. )
  549. # We set the parent as being the ClassDef of 'object' as that
  550. # is where this method originally comes from
  551. node.parent = AstroidManager().builtins_module["object"]
  552. return bases.BoundMethod(proxy=node, bound=_get_bound_node(self))
  553. class BoundMethodModel(FunctionModel):
  554. @property
  555. def attr___func__(self):
  556. return self._instance._proxied._proxied
  557. @property
  558. def attr___self__(self):
  559. return self._instance.bound
  560. class GeneratorBaseModel(FunctionModel, ContextManagerModel):
  561. def __init__(self, gen_module: nodes.Module):
  562. super().__init__()
  563. for name, values in gen_module.locals.items():
  564. method = values[0]
  565. if isinstance(method, nodes.FunctionDef):
  566. method = bases.BoundMethod(method, _get_bound_node(self))
  567. def patched(cls, meth=method):
  568. return meth
  569. setattr(type(self), IMPL_PREFIX + name, property(patched))
  570. @property
  571. def attr___name__(self):
  572. return node_classes.Const(
  573. value=self._instance.parent.name, parent=self._instance
  574. )
  575. @property
  576. def attr___doc__(self):
  577. return node_classes.Const(
  578. value=getattr(self._instance.parent.doc_node, "value", None),
  579. parent=self._instance,
  580. )
  581. class GeneratorModel(GeneratorBaseModel):
  582. def __init__(self):
  583. super().__init__(AstroidManager().builtins_module["generator"])
  584. class AsyncGeneratorModel(GeneratorBaseModel):
  585. def __init__(self):
  586. super().__init__(AstroidManager().builtins_module["async_generator"])
  587. class InstanceModel(ObjectModel):
  588. @property
  589. def attr___class__(self):
  590. return self._instance._proxied
  591. @property
  592. def attr___module__(self):
  593. return node_classes.Const(self._instance.root().qname())
  594. @property
  595. def attr___doc__(self):
  596. return node_classes.Const(getattr(self._instance.doc_node, "value", None))
  597. @property
  598. def attr___dict__(self):
  599. return _dunder_dict(self._instance, self._instance.instance_attrs)
  600. # Exception instances
  601. class ExceptionInstanceModel(InstanceModel):
  602. @property
  603. def attr_args(self) -> nodes.Tuple:
  604. return nodes.Tuple(parent=self._instance)
  605. @property
  606. def attr___traceback__(self):
  607. builtins_ast_module = AstroidManager().builtins_module
  608. traceback_type = builtins_ast_module[types.TracebackType.__name__]
  609. return traceback_type.instantiate_class()
  610. class SyntaxErrorInstanceModel(ExceptionInstanceModel):
  611. @property
  612. def attr_text(self):
  613. return node_classes.Const("")
  614. class GroupExceptionInstanceModel(ExceptionInstanceModel):
  615. @property
  616. def attr_exceptions(self) -> nodes.Tuple:
  617. return node_classes.Tuple(parent=self._instance)
  618. class OSErrorInstanceModel(ExceptionInstanceModel):
  619. @property
  620. def attr_filename(self):
  621. return node_classes.Const("")
  622. @property
  623. def attr_errno(self):
  624. return node_classes.Const(0)
  625. @property
  626. def attr_strerror(self):
  627. return node_classes.Const("")
  628. attr_filename2 = attr_filename
  629. class ImportErrorInstanceModel(ExceptionInstanceModel):
  630. @property
  631. def attr_name(self):
  632. return node_classes.Const("")
  633. @property
  634. def attr_path(self):
  635. return node_classes.Const("")
  636. class UnicodeDecodeErrorInstanceModel(ExceptionInstanceModel):
  637. @property
  638. def attr_object(self):
  639. return node_classes.Const(b"")
  640. BUILTIN_EXCEPTIONS = {
  641. "builtins.SyntaxError": SyntaxErrorInstanceModel,
  642. "builtins.ExceptionGroup": GroupExceptionInstanceModel,
  643. "builtins.ImportError": ImportErrorInstanceModel,
  644. "builtins.UnicodeDecodeError": UnicodeDecodeErrorInstanceModel,
  645. # These are all similar to OSError in terms of attributes
  646. "builtins.OSError": OSErrorInstanceModel,
  647. "builtins.BlockingIOError": OSErrorInstanceModel,
  648. "builtins.BrokenPipeError": OSErrorInstanceModel,
  649. "builtins.ChildProcessError": OSErrorInstanceModel,
  650. "builtins.ConnectionAbortedError": OSErrorInstanceModel,
  651. "builtins.ConnectionError": OSErrorInstanceModel,
  652. "builtins.ConnectionRefusedError": OSErrorInstanceModel,
  653. "builtins.ConnectionResetError": OSErrorInstanceModel,
  654. "builtins.FileExistsError": OSErrorInstanceModel,
  655. "builtins.FileNotFoundError": OSErrorInstanceModel,
  656. "builtins.InterruptedError": OSErrorInstanceModel,
  657. "builtins.IsADirectoryError": OSErrorInstanceModel,
  658. "builtins.NotADirectoryError": OSErrorInstanceModel,
  659. "builtins.PermissionError": OSErrorInstanceModel,
  660. "builtins.ProcessLookupError": OSErrorInstanceModel,
  661. "builtins.TimeoutError": OSErrorInstanceModel,
  662. }
  663. class DictModel(ObjectModel):
  664. @property
  665. def attr___class__(self):
  666. return self._instance._proxied
  667. def _generic_dict_attribute(self, obj, name):
  668. """Generate a bound method that can infer the given *obj*."""
  669. class DictMethodBoundMethod(astroid.BoundMethod):
  670. def infer_call_result(
  671. self,
  672. caller: SuccessfulInferenceResult | None,
  673. context: InferenceContext | None = None,
  674. ) -> Iterator[InferenceResult]:
  675. yield obj
  676. meth = next(self._instance._proxied.igetattr(name), None)
  677. return DictMethodBoundMethod(proxy=meth, bound=self._instance)
  678. @property
  679. def attr_items(self):
  680. from astroid import objects # pylint: disable=import-outside-toplevel
  681. elems = []
  682. obj = node_classes.List(parent=self._instance)
  683. for key, value in self._instance.items:
  684. elem = node_classes.Tuple(parent=obj)
  685. elem.postinit((key, value))
  686. elems.append(elem)
  687. obj.postinit(elts=elems)
  688. items_obj = objects.DictItems(obj)
  689. return self._generic_dict_attribute(items_obj, "items")
  690. @property
  691. def attr_keys(self):
  692. from astroid import objects # pylint: disable=import-outside-toplevel
  693. keys = [key for (key, _) in self._instance.items]
  694. obj = node_classes.List(parent=self._instance)
  695. obj.postinit(elts=keys)
  696. keys_obj = objects.DictKeys(obj)
  697. return self._generic_dict_attribute(keys_obj, "keys")
  698. @property
  699. def attr_values(self):
  700. from astroid import objects # pylint: disable=import-outside-toplevel
  701. values = [value for (_, value) in self._instance.items]
  702. obj = node_classes.List(parent=self._instance)
  703. obj.postinit(values)
  704. values_obj = objects.DictValues(obj)
  705. return self._generic_dict_attribute(values_obj, "values")
  706. class PropertyModel(ObjectModel):
  707. """Model for a builtin property."""
  708. def _init_function(self, name):
  709. function = nodes.FunctionDef(
  710. name=name,
  711. parent=self._instance,
  712. lineno=self._instance.lineno,
  713. col_offset=self._instance.col_offset,
  714. end_lineno=self._instance.end_lineno,
  715. end_col_offset=self._instance.end_col_offset,
  716. )
  717. args = nodes.Arguments(parent=function, vararg=None, kwarg=None)
  718. args.postinit(
  719. args=[],
  720. defaults=[],
  721. kwonlyargs=[],
  722. kw_defaults=[],
  723. annotations=[],
  724. posonlyargs=[],
  725. posonlyargs_annotations=[],
  726. kwonlyargs_annotations=[],
  727. )
  728. function.postinit(args=args, body=[])
  729. return function
  730. @property
  731. def attr_fget(self):
  732. func = self._instance
  733. class PropertyFuncAccessor(nodes.FunctionDef):
  734. def infer_call_result(
  735. self,
  736. caller: SuccessfulInferenceResult | None,
  737. context: InferenceContext | None = None,
  738. ) -> Iterator[InferenceResult]:
  739. nonlocal func
  740. if caller and len(caller.args) != 1:
  741. raise InferenceError(
  742. "fget() needs a single argument", target=self, context=context
  743. )
  744. yield from func.function.infer_call_result(
  745. caller=caller, context=context
  746. )
  747. property_accessor = PropertyFuncAccessor(
  748. name="fget",
  749. parent=self._instance,
  750. lineno=self._instance.lineno,
  751. col_offset=self._instance.col_offset,
  752. end_lineno=self._instance.end_lineno,
  753. end_col_offset=self._instance.end_col_offset,
  754. )
  755. property_accessor.postinit(args=func.args, body=func.body)
  756. return property_accessor
  757. @property
  758. def attr_fset(self):
  759. func = self._instance
  760. def find_setter(func: Property) -> nodes.FunctionDef | None:
  761. """
  762. Given a property, find the corresponding setter function and returns it.
  763. :param func: property for which the setter has to be found
  764. :return: the setter function or None
  765. """
  766. for target in [
  767. t for t in func.parent.get_children() if t.name == func.function.name
  768. ]:
  769. for dec_name in target.decoratornames():
  770. if dec_name.endswith(func.function.name + ".setter"):
  771. return target
  772. return None
  773. func_setter = find_setter(func)
  774. if not func_setter:
  775. raise InferenceError(
  776. f"Unable to find the setter of property {func.function.name}"
  777. )
  778. class PropertyFuncAccessor(nodes.FunctionDef):
  779. def infer_call_result(
  780. self,
  781. caller: SuccessfulInferenceResult | None,
  782. context: InferenceContext | None = None,
  783. ) -> Iterator[InferenceResult]:
  784. nonlocal func_setter
  785. if caller and len(caller.args) != 2:
  786. raise InferenceError(
  787. "fset() needs two arguments", target=self, context=context
  788. )
  789. yield from func_setter.infer_call_result(caller=caller, context=context)
  790. property_accessor = PropertyFuncAccessor(
  791. name="fset",
  792. parent=self._instance,
  793. lineno=self._instance.lineno,
  794. col_offset=self._instance.col_offset,
  795. end_lineno=self._instance.end_lineno,
  796. end_col_offset=self._instance.end_col_offset,
  797. )
  798. property_accessor.postinit(args=func_setter.args, body=func_setter.body)
  799. return property_accessor
  800. @property
  801. def attr_setter(self):
  802. return self._init_function("setter")
  803. @property
  804. def attr_deleter(self):
  805. return self._init_function("deleter")
  806. @property
  807. def attr_getter(self):
  808. return self._init_function("getter")
  809. # pylint: enable=import-outside-toplevel