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"""
15
16from __future__ import annotations
17
18import itertools
19from typing import TYPE_CHECKING
20
21import astroid
22from astroid import nodes
23from astroid.exceptions import AttributeInferenceError
24
25if TYPE_CHECKING:
26 from astroid.context import InferenceContext
27
28
29def _lookup_in_mro(node, name) -> list:
30 attrs = node.locals.get(name, [])
31
32 nodes_ = itertools.chain.from_iterable(
33 ancestor.locals.get(name, []) for ancestor in node.ancestors(recurs=True)
34 )
35 values = list(itertools.chain(attrs, nodes_))
36 if not values:
37 raise AttributeInferenceError(attribute=name, target=node)
38
39 return values
40
41
42def lookup(
43 node: nodes.NodeNG, name: str, context: InferenceContext | None = None
44) -> list:
45 """Lookup the given special method name in the given *node*.
46
47 If the special method was found, then a list of attributes
48 will be returned. Otherwise, `astroid.AttributeInferenceError`
49 is going to be raised.
50 """
51 if isinstance(node, (nodes.List, nodes.Tuple, nodes.Const, nodes.Dict, nodes.Set)):
52 return _builtin_lookup(node, name)
53 if isinstance(node, astroid.Instance):
54 return _lookup_in_mro(node, name)
55 if isinstance(node, nodes.ClassDef):
56 return _class_lookup(node, name, context=context)
57
58 raise AttributeInferenceError(attribute=name, target=node)
59
60
61def _class_lookup(
62 node: nodes.ClassDef, name: str, context: InferenceContext | None = None
63) -> list:
64 metaclass = node.metaclass(context=context)
65 # An explicit metaclass may infer to a non-class node (e.g. a function),
66 # which has no MRO to look the special method up in.
67 if not isinstance(metaclass, nodes.ClassDef):
68 raise AttributeInferenceError(attribute=name, target=node)
69
70 return _lookup_in_mro(metaclass, name)
71
72
73def _builtin_lookup(node, name) -> list:
74 values = node.locals.get(name, [])
75 if not values:
76 raise AttributeInferenceError(attribute=name, target=node)
77
78 return values