objectmodel.py 30 KB

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