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.exceptions import AttributeInferenceError
22
23if TYPE_CHECKING:
24 from astroid import nodes
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(
51 node, (astroid.List, astroid.Tuple, astroid.Const, astroid.Dict, astroid.Set)
52 ):
53 return _builtin_lookup(node, name)
54 if isinstance(node, astroid.Instance):
55 return _lookup_in_mro(node, name)
56 if isinstance(node, astroid.ClassDef):
57 return _class_lookup(node, name, context=context)
58
59 raise AttributeInferenceError(attribute=name, target=node)
60
61
62def _class_lookup(
63 node: nodes.ClassDef, name: str, context: InferenceContext | None = None
64) -> list:
65 metaclass = node.metaclass(context=context)
66 if metaclass is None:
67 raise AttributeInferenceError(attribute=name, target=node)
68
69 return _lookup_in_mro(metaclass, name)
70
71
72def _builtin_lookup(node, name) -> list:
73 values = node.locals.get(name, [])
74 if not values:
75 raise AttributeInferenceError(attribute=name, target=node)
76
77 return values