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 _drop_overloads(values: list) -> list:
30 """Drop ``typing.overload`` stubs when a real implementation is present.
31
32 A ``@overload``-decorated definition only declares a signature; the last,
33 undecorated definition is the one actually bound at runtime. Callers take
34 the first returned value, so the stubs must not shadow it.
35 """
36 implementations = [
37 value
38 for value in values
39 if not (
40 isinstance(value, nodes.FunctionDef)
41 and "typing.overload" in value.decoratornames()
42 )
43 ]
44 return implementations or values
45
46
47def _lookup_in_mro(node, name) -> list:
48 attrs = node.locals.get(name, [])
49
50 nodes_ = itertools.chain.from_iterable(
51 ancestor.locals.get(name, []) for ancestor in node.ancestors(recurs=True)
52 )
53 values = list(itertools.chain(attrs, nodes_))
54 if not values:
55 raise AttributeInferenceError(attribute=name, target=node)
56
57 return _drop_overloads(values)
58
59
60def lookup(
61 node: nodes.NodeNG, name: str, context: InferenceContext | None = None
62) -> list:
63 """Lookup the given special method name in the given *node*.
64
65 If the special method was found, then a list of attributes
66 will be returned. Otherwise, `astroid.AttributeInferenceError`
67 is going to be raised.
68 """
69 if isinstance(node, (nodes.List, nodes.Tuple, nodes.Const, nodes.Dict, nodes.Set)):
70 return _builtin_lookup(node, name)
71 if isinstance(node, astroid.Instance):
72 return _lookup_in_mro(node, name)
73 if isinstance(node, nodes.ClassDef):
74 return _class_lookup(node, name, context=context)
75
76 raise AttributeInferenceError(attribute=name, target=node)
77
78
79def _class_lookup(
80 node: nodes.ClassDef, name: str, context: InferenceContext | None = None
81) -> list:
82 metaclass = node.metaclass(context=context)
83 # An explicit metaclass may infer to a non-class node (e.g. a function),
84 # which has no MRO to look the special method up in.
85 if not isinstance(metaclass, nodes.ClassDef):
86 raise AttributeInferenceError(attribute=name, target=node)
87
88 return _lookup_in_mro(metaclass, name)
89
90
91def _builtin_lookup(node, name) -> list:
92 values = node.locals.get(name, [])
93 if not values:
94 raise AttributeInferenceError(attribute=name, target=node)
95
96 return values