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"""Contains logic for retrieving special methods.
6
7This implementation does not rely on the dot attribute access
8logic, found in ``.getattr()``. The difference between these two
9is that the dunder methods are looked with the type slots
10(you can find more about these here
11http://lucumr.pocoo.org/2014/8/16/the-python-i-would-like-to-see/)
12As such, the lookup for the special methods is actually simpler than
13the dot attribute access.
14"""
15from __future__ import annotations
16
17import itertools
18from typing import TYPE_CHECKING
19
20import astroid
21from astroid import nodes
22from astroid.exceptions import AttributeInferenceError
23
24if TYPE_CHECKING:
25 from astroid.context import InferenceContext
26
27
28def _lookup_in_mro(node, name) -> list:
29 attrs = node.locals.get(name, [])
30
31 nodes_ = itertools.chain.from_iterable(
32 ancestor.locals.get(name, []) for ancestor in node.ancestors(recurs=True)
33 )
34 values = list(itertools.chain(attrs, nodes_))
35 if not values:
36 raise AttributeInferenceError(attribute=name, target=node)
37
38 return values
39
40
41def lookup(
42 node: nodes.NodeNG, name: str, context: InferenceContext | None = None
43) -> list:
44 """Lookup the given special method name in the given *node*.
45
46 If the special method was found, then a list of attributes
47 will be returned. Otherwise, `astroid.AttributeInferenceError`
48 is going to be raised.
49 """
50 if isinstance(node, (nodes.List, nodes.Tuple, nodes.Const, nodes.Dict, nodes.Set)):
51 return _builtin_lookup(node, name)
52 if isinstance(node, astroid.Instance):
53 return _lookup_in_mro(node, name)
54 if isinstance(node, nodes.ClassDef):
55 return _class_lookup(node, name, context=context)
56
57 raise AttributeInferenceError(attribute=name, target=node)
58
59
60def _class_lookup(
61 node: nodes.ClassDef, name: str, context: InferenceContext | None = None
62) -> list:
63 metaclass = node.metaclass(context=context)
64 if metaclass is None:
65 raise AttributeInferenceError(attribute=name, target=node)
66
67 return _lookup_in_mro(metaclass, name)
68
69
70def _builtin_lookup(node, name) -> list:
71 values = node.locals.get(name, [])
72 if not values:
73 raise AttributeInferenceError(attribute=name, target=node)
74
75 return values