| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358 |
- # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
- # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
- # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
- """Some functions that may be useful for various checkers."""
- from __future__ import annotations
- import _string
- import builtins
- import fnmatch
- import itertools
- import numbers
- import re
- import string
- from collections.abc import Callable, Iterable, Iterator
- from functools import lru_cache, partial
- from re import Match
- from typing import TYPE_CHECKING, TypeVar
- import astroid
- import astroid.exceptions
- import astroid.helpers
- from astroid import TooManyLevelsError, bases, nodes, objects, util
- from astroid.context import InferenceContext
- from astroid.exceptions import AstroidError
- from astroid.nodes._base_nodes import ImportNode, Statement
- from astroid.typing import InferenceResult, SuccessfulInferenceResult
- from pylint.constants import TYPING_NEVER, TYPING_NORETURN
- if TYPE_CHECKING:
- from pylint.checkers import BaseChecker
- _NodeT = TypeVar("_NodeT", bound=nodes.NodeNG)
- _CheckerT = TypeVar("_CheckerT", bound="BaseChecker")
- AstCallbackMethod = Callable[[_CheckerT, _NodeT], None]
- COMP_NODE_TYPES = (
- nodes.ListComp,
- nodes.SetComp,
- nodes.DictComp,
- nodes.GeneratorExp,
- )
- EXCEPTIONS_MODULE = "builtins"
- ABC_MODULES = {"abc", "_py_abc"}
- ABC_METHODS = {
- "abc.abstractproperty",
- "abc.abstractmethod",
- "abc.abstractclassmethod",
- "abc.abstractstaticmethod",
- }
- TYPING_PROTOCOLS = frozenset(
- {"typing.Protocol", "typing_extensions.Protocol", ".Protocol"}
- )
- COMMUTATIVE_OPERATORS = frozenset({"*", "+", "^", "&", "|"})
- ITER_METHOD = "__iter__"
- AITER_METHOD = "__aiter__"
- NEXT_METHOD = "__next__"
- GETITEM_METHOD = "__getitem__"
- CLASS_GETITEM_METHOD = "__class_getitem__"
- SETITEM_METHOD = "__setitem__"
- DELITEM_METHOD = "__delitem__"
- CONTAINS_METHOD = "__contains__"
- KEYS_METHOD = "keys"
- # Dictionary which maps the number of expected parameters a
- # special method can have to a set of special methods.
- # The following keys are used to denote the parameters restrictions:
- #
- # * None: variable number of parameters
- # * number: exactly that number of parameters
- # * tuple: these are the odd ones. Basically it means that the function
- # can work with any number of arguments from that tuple,
- # although it's best to implement it in order to accept
- # all of them.
- _SPECIAL_METHODS_PARAMS = {
- None: ("__new__", "__init__", "__call__", "__init_subclass__"),
- 0: (
- "__del__",
- "__repr__",
- "__str__",
- "__bytes__",
- "__hash__",
- "__bool__",
- "__dir__",
- "__len__",
- "__length_hint__",
- "__iter__",
- "__reversed__",
- "__neg__",
- "__pos__",
- "__abs__",
- "__invert__",
- "__complex__",
- "__int__",
- "__float__",
- "__index__",
- "__trunc__",
- "__floor__",
- "__ceil__",
- "__enter__",
- "__aenter__",
- "__getnewargs_ex__",
- "__getnewargs__",
- "__getstate__",
- "__reduce__",
- "__copy__",
- "__unicode__",
- "__nonzero__",
- "__await__",
- "__aiter__",
- "__anext__",
- "__fspath__",
- "__subclasses__",
- ),
- 1: (
- "__format__",
- "__lt__",
- "__le__",
- "__eq__",
- "__ne__",
- "__gt__",
- "__ge__",
- "__getattr__",
- "__getattribute__",
- "__delattr__",
- "__delete__",
- "__instancecheck__",
- "__subclasscheck__",
- "__getitem__",
- "__missing__",
- "__delitem__",
- "__contains__",
- "__add__",
- "__sub__",
- "__mul__",
- "__truediv__",
- "__floordiv__",
- "__rfloordiv__",
- "__mod__",
- "__divmod__",
- "__lshift__",
- "__rshift__",
- "__and__",
- "__xor__",
- "__or__",
- "__radd__",
- "__rsub__",
- "__rmul__",
- "__rtruediv__",
- "__rmod__",
- "__rdivmod__",
- "__rpow__",
- "__rlshift__",
- "__rrshift__",
- "__rand__",
- "__rxor__",
- "__ror__",
- "__iadd__",
- "__isub__",
- "__imul__",
- "__itruediv__",
- "__ifloordiv__",
- "__imod__",
- "__ilshift__",
- "__irshift__",
- "__iand__",
- "__ixor__",
- "__ior__",
- "__ipow__",
- "__setstate__",
- "__reduce_ex__",
- "__deepcopy__",
- "__cmp__",
- "__matmul__",
- "__rmatmul__",
- "__imatmul__",
- "__div__",
- ),
- 2: ("__setattr__", "__get__", "__set__", "__setitem__", "__set_name__"),
- 3: ("__exit__", "__aexit__"),
- (0, 1): ("__round__",),
- (1, 2): ("__pow__",),
- }
- SPECIAL_METHODS_PARAMS = {
- name: params
- for params, methods in _SPECIAL_METHODS_PARAMS.items()
- for name in methods
- }
- PYMETHODS = set(SPECIAL_METHODS_PARAMS)
- SUBSCRIPTABLE_CLASSES_PEP585 = frozenset(
- (
- "builtins.tuple",
- "builtins.list",
- "builtins.dict",
- "builtins.set",
- "builtins.frozenset",
- "builtins.type",
- "collections.deque",
- "collections.defaultdict",
- "collections.OrderedDict",
- "collections.Counter",
- "collections.ChainMap",
- "_collections_abc.Awaitable",
- "_collections_abc.Coroutine",
- "_collections_abc.AsyncIterable",
- "_collections_abc.AsyncIterator",
- "_collections_abc.AsyncGenerator",
- "_collections_abc.Iterable",
- "_collections_abc.Iterator",
- "_collections_abc.Generator",
- "_collections_abc.Reversible",
- "_collections_abc.Container",
- "_collections_abc.Collection",
- "_collections_abc.Callable",
- "_collections_abc.Set",
- "_collections_abc.MutableSet",
- "_collections_abc.Mapping",
- "_collections_abc.MutableMapping",
- "_collections_abc.Sequence",
- "_collections_abc.MutableSequence",
- "_collections_abc.ByteString",
- "_collections_abc.MappingView",
- "_collections_abc.KeysView",
- "_collections_abc.ItemsView",
- "_collections_abc.ValuesView",
- "contextlib.AbstractContextManager",
- "contextlib.AbstractAsyncContextManager",
- "re.Pattern",
- "re.Match",
- )
- )
- SINGLETON_VALUES = {True, False, None}
- TERMINATING_FUNCS_QNAMES = frozenset(
- {
- "_sitebuiltins.Quitter",
- "sys.exit",
- "posix._exit",
- "nt._exit",
- "unittest.case.TestCase.fail",
- }
- )
- class NoSuchArgumentError(Exception):
- pass
- class InferredTypeError(Exception):
- pass
- def get_all_elements(
- node: nodes.NodeNG,
- ) -> Iterable[nodes.NodeNG]:
- """Recursively returns all atoms in nested lists and tuples."""
- if isinstance(node, (nodes.Tuple, nodes.List)):
- for child in node.elts:
- yield from get_all_elements(child)
- else:
- yield node
- def is_super(node: nodes.NodeNG) -> bool:
- """Return True if the node is referencing the "super" builtin function."""
- if getattr(node, "name", None) == "super" and node.root().name == "builtins":
- return True
- return False
- def is_error(node: nodes.FunctionDef) -> bool:
- """Return true if the given function node only raises an exception."""
- return len(node.body) == 1 and isinstance(node.body[0], nodes.Raise)
- builtins = builtins.__dict__.copy() # type: ignore[assignment]
- SPECIAL_BUILTINS = ("__builtins__",) # '__path__', '__file__')
- def is_builtin_object(node: nodes.NodeNG) -> bool:
- """Returns True if the given node is an object from the __builtin__ module."""
- return node and node.root().name == "builtins" # type: ignore[no-any-return]
- def is_builtin(name: str) -> bool:
- """Return true if <name> could be considered as a builtin defined by python."""
- return name in builtins or name in SPECIAL_BUILTINS # type: ignore[operator]
- def is_defined_in_scope(
- var_node: nodes.NodeNG,
- varname: str,
- scope: nodes.NodeNG,
- ) -> bool:
- return defnode_in_scope(var_node, varname, scope) is not None
- # pylint: disable = too-many-branches
- def defnode_in_scope(
- var_node: nodes.NodeNG,
- varname: str,
- scope: nodes.NodeNG,
- ) -> nodes.NodeNG | None:
- if isinstance(scope, nodes.If):
- for node in scope.body:
- if isinstance(node, nodes.Nonlocal) and varname in node.names:
- return node
- if isinstance(node, nodes.Assign):
- for target in node.targets:
- if isinstance(target, nodes.AssignName) and target.name == varname:
- return target
- elif isinstance(scope, (COMP_NODE_TYPES, nodes.For)):
- for ass_node in scope.nodes_of_class(nodes.AssignName):
- if ass_node.name == varname:
- return ass_node
- elif isinstance(scope, nodes.With):
- for expr, ids in scope.items:
- if expr.parent_of(var_node):
- break
- if ids and isinstance(ids, nodes.AssignName) and ids.name == varname:
- return ids
- elif isinstance(scope, (nodes.Lambda, nodes.FunctionDef)):
- if scope.args.is_argument(varname):
- # If the name is found inside a default value
- # of a function, then let the search continue
- # in the parent's tree.
- if scope.args.parent_of(var_node):
- try:
- scope.args.default_value(varname)
- scope = scope.parent
- defnode = defnode_in_scope(var_node, varname, scope)
- except astroid.NoDefault:
- pass
- else:
- return defnode
- return scope
- if getattr(scope, "name", None) == varname:
- return scope
- elif isinstance(scope, nodes.ExceptHandler):
- if isinstance(scope.name, nodes.AssignName):
- ass_node = scope.name
- if ass_node.name == varname:
- return ass_node
- return None
- def is_defined_before(var_node: nodes.Name) -> bool:
- """Check if the given variable node is defined before.
- Verify that the variable node is defined by a parent node
- (e.g. if or with) earlier than `var_node`, or is defined by a
- (list, set, dict, or generator comprehension, lambda)
- or in a previous sibling node on the same line
- (statement_defining ; statement_using).
- """
- varname = var_node.name
- for parent in var_node.node_ancestors():
- defnode = defnode_in_scope(var_node, varname, parent)
- if defnode is None:
- continue
- defnode_scope = defnode.scope()
- if isinstance(
- defnode_scope, (*COMP_NODE_TYPES, nodes.Lambda, nodes.FunctionDef)
- ):
- # Avoid the case where var_node_scope is a nested function
- if isinstance(defnode_scope, nodes.FunctionDef):
- var_node_scope = var_node.scope()
- if var_node_scope is not defnode_scope and isinstance(
- var_node_scope, nodes.FunctionDef
- ):
- return False
- return True
- if defnode.lineno < var_node.lineno:
- return True
- # `defnode` and `var_node` on the same line
- for defnode_anc in defnode.node_ancestors():
- if defnode_anc.lineno != var_node.lineno:
- continue
- if isinstance(
- defnode_anc,
- (
- nodes.For,
- nodes.While,
- nodes.With,
- nodes.Try,
- nodes.ExceptHandler,
- ),
- ):
- return True
- # possibly multiple statements on the same line using semicolon separator
- stmt = var_node.statement()
- _node = stmt.previous_sibling()
- lineno = stmt.fromlineno
- while _node and _node.fromlineno == lineno:
- for assign_node in _node.nodes_of_class(nodes.AssignName):
- if assign_node.name == varname:
- return True
- for imp_node in _node.nodes_of_class((nodes.ImportFrom, nodes.Import)):
- if varname in [name[1] or name[0] for name in imp_node.names]:
- return True
- _node = _node.previous_sibling()
- return False
- def is_default_argument(node: nodes.NodeNG, scope: nodes.NodeNG | None = None) -> bool:
- """Return true if the given Name node is used in function or lambda
- default argument's value.
- """
- if not scope:
- scope = node.scope()
- if isinstance(scope, (nodes.FunctionDef, nodes.Lambda)):
- all_defaults = itertools.chain(
- scope.args.defaults, (d for d in scope.args.kw_defaults if d is not None)
- )
- return any(
- default_name_node is node
- for default_node in all_defaults
- for default_name_node in default_node.nodes_of_class(nodes.Name)
- )
- return False
- def is_func_decorator(node: nodes.NodeNG) -> bool:
- """Return true if the name is used in function decorator."""
- for parent in node.node_ancestors():
- if isinstance(parent, nodes.Decorators):
- return True
- if parent.is_statement or isinstance(
- parent,
- (
- nodes.Lambda,
- nodes.ComprehensionScope,
- nodes.ListComp,
- ),
- ):
- break
- return False
- def is_ancestor_name(frame: nodes.ClassDef, node: nodes.NodeNG) -> bool:
- """Return whether `frame` is an nodes.Class node with `node` in the
- subtree of its bases attribute.
- """
- if not isinstance(frame, nodes.ClassDef):
- return False
- return any(node in base.nodes_of_class(nodes.Name) for base in frame.bases)
- def is_being_called(node: nodes.NodeNG) -> bool:
- """Return True if node is the function being called in a Call node."""
- return isinstance(node.parent, nodes.Call) and node.parent.func is node
- def assign_parent(node: nodes.NodeNG) -> nodes.NodeNG:
- """Return the higher parent which is not an AssignName, Tuple or List node."""
- while node and isinstance(node, (nodes.AssignName, nodes.Tuple, nodes.List)):
- node = node.parent
- return node
- def overrides_a_method(class_node: nodes.ClassDef, name: str) -> bool:
- """Return True if <name> is a method overridden from an ancestor
- which is not the base object class.
- """
- for ancestor in class_node.ancestors():
- if ancestor.name == "object":
- continue
- if name in ancestor and isinstance(ancestor[name], nodes.FunctionDef):
- return True
- return False
- def only_required_for_messages(
- *messages: str,
- ) -> Callable[
- [AstCallbackMethod[_CheckerT, _NodeT]], AstCallbackMethod[_CheckerT, _NodeT]
- ]:
- """Decorator to store messages that are handled by a checker method as an
- attribute of the function object.
- This information is used by ``ASTWalker`` to decide whether to call the decorated
- method or not. If none of the messages is enabled, the method will be skipped.
- Therefore, the list of messages must be well maintained at all times!
- This decorator only has an effect on ``visit_*`` and ``leave_*`` methods
- of a class inheriting from ``BaseChecker``.
- """
- def store_messages(
- func: AstCallbackMethod[_CheckerT, _NodeT],
- ) -> AstCallbackMethod[_CheckerT, _NodeT]:
- func.checks_msgs = messages # type: ignore[attr-defined]
- return func
- return store_messages
- class IncompleteFormatString(Exception):
- """A format string ended in the middle of a format specifier."""
- class UnsupportedFormatCharacter(Exception):
- """A format character in a format string is not one of the supported
- format characters.
- """
- def __init__(self, index: int) -> None:
- super().__init__(index)
- self.index = index
- def parse_format_string(
- format_string: str,
- ) -> tuple[set[str], int, dict[str, str], list[str]]:
- """Parses a format string, returning a tuple (keys, num_args).
- Where 'keys' is the set of mapping keys in the format string, and 'num_args' is the number
- of arguments required by the format string. Raises IncompleteFormatString or
- UnsupportedFormatCharacter if a parse error occurs.
- """
- keys = set()
- key_types = {}
- pos_types = []
- num_args = 0
- def next_char(i: int) -> tuple[int, str]:
- i += 1
- if i == len(format_string):
- raise IncompleteFormatString
- return (i, format_string[i])
- i = 0
- while i < len(format_string):
- char = format_string[i]
- if char == "%":
- i, char = next_char(i)
- # Parse the mapping key (optional).
- key = None
- if char == "(":
- depth = 1
- i, char = next_char(i)
- key_start = i
- while depth != 0:
- if char == "(":
- depth += 1
- elif char == ")":
- depth -= 1
- i, char = next_char(i)
- key_end = i - 1
- key = format_string[key_start:key_end]
- # Parse the conversion flags (optional).
- while char in "#0- +":
- i, char = next_char(i)
- # Parse the minimum field width (optional).
- if char == "*":
- num_args += 1
- i, char = next_char(i)
- else:
- while char in string.digits:
- i, char = next_char(i)
- # Parse the precision (optional).
- if char == ".":
- i, char = next_char(i)
- if char == "*":
- num_args += 1
- i, char = next_char(i)
- else:
- while char in string.digits:
- i, char = next_char(i)
- # Parse the length modifier (optional).
- if char in "hlL":
- i, char = next_char(i)
- # Parse the conversion type (mandatory).
- flags = "diouxXeEfFgGcrs%a"
- if char not in flags:
- raise UnsupportedFormatCharacter(i)
- if key:
- keys.add(key)
- key_types[key] = char
- elif char != "%":
- num_args += 1
- pos_types.append(char)
- i += 1
- return keys, num_args, key_types, pos_types
- def split_format_field_names(
- format_string: str,
- ) -> tuple[str, Iterable[tuple[bool, str]]]:
- try:
- return _string.formatter_field_name_split(format_string) # type: ignore[no-any-return]
- except ValueError as e:
- raise IncompleteFormatString() from e
- def collect_string_fields(format_string: str) -> Iterable[str | None]:
- """Given a format string, return an iterator
- of all the valid format fields.
- It handles nested fields as well.
- """
- formatter = string.Formatter()
- # pylint: disable = too-many-try-statements
- try:
- parseiterator = formatter.parse(format_string)
- for result in parseiterator:
- if all(item is None for item in result[1:]):
- # not a replacement format
- continue
- name = result[1]
- nested = result[2]
- yield name
- if nested:
- yield from collect_string_fields(nested)
- except ValueError as exc:
- # Probably the format string is invalid.
- if exc.args[0].startswith("cannot switch from manual"):
- # On Jython, parsing a string with both manual
- # and automatic positions will fail with a ValueError,
- # while on CPython it will simply return the fields,
- # the validation being done in the interpreter (?).
- # We're just returning two mixed fields in order
- # to trigger the format-combined-specification check.
- yield ""
- yield "1"
- return
- raise IncompleteFormatString(format_string) from exc
- def parse_format_method_string(
- format_string: str,
- ) -> tuple[list[tuple[str, list[tuple[bool, str]]]], int, int]:
- """Parses a PEP 3101 format string, returning a tuple of
- (keyword_arguments, implicit_pos_args_cnt, explicit_pos_args).
- keyword_arguments is the set of mapping keys in the format string, implicit_pos_args_cnt
- is the number of arguments required by the format string and
- explicit_pos_args is the number of arguments passed with the position.
- """
- keyword_arguments = []
- implicit_pos_args_cnt = 0
- explicit_pos_args = set()
- for name in collect_string_fields(format_string):
- if name and str(name).isdigit():
- explicit_pos_args.add(str(name))
- elif name:
- keyname, fielditerator = split_format_field_names(name)
- if isinstance(keyname, numbers.Number):
- explicit_pos_args.add(str(keyname))
- try:
- keyword_arguments.append((keyname, list(fielditerator)))
- except ValueError as e:
- raise IncompleteFormatString() from e
- else:
- implicit_pos_args_cnt += 1
- return keyword_arguments, implicit_pos_args_cnt, len(explicit_pos_args)
- def is_attr_protected(attrname: str) -> bool:
- """Return True if attribute name is protected (start with _ and some other
- details), False otherwise.
- """
- return (
- attrname[0] == "_"
- and attrname != "_"
- and not (attrname.startswith("__") and attrname.endswith("__"))
- )
- def node_frame_class(node: nodes.NodeNG) -> nodes.ClassDef | None:
- """Return the class that is wrapping the given node.
- The function returns a class for a method node (or a staticmethod or a
- classmethod), otherwise it returns `None`.
- """
- klass = node.frame()
- nodes_to_check = (
- nodes.NodeNG,
- astroid.UnboundMethod,
- astroid.BaseInstance,
- )
- while (
- klass
- and isinstance(klass, nodes_to_check)
- and not isinstance(klass, nodes.ClassDef)
- ):
- if klass.parent is None:
- return None
- klass = klass.parent.frame()
- return klass
- def get_outer_class(class_node: nodes.ClassDef) -> nodes.ClassDef | None:
- """Return the class that is the outer class of given (nested) class_node."""
- parent_klass = class_node.parent.frame()
- return parent_klass if isinstance(parent_klass, nodes.ClassDef) else None
- def is_attr_private(attrname: str) -> Match[str] | None:
- """Check that attribute name is private (at least two leading underscores,
- at most one trailing underscore).
- """
- regex = re.compile("^_{2,10}.*[^_]+_?$")
- return regex.match(attrname)
- def get_argument_from_call(
- call_node: nodes.Call, position: int | None = None, keyword: str | None = None
- ) -> nodes.Name:
- """Returns the specified argument from a function call.
- :param nodes.Call call_node: Node representing a function call to check.
- :param int position: position of the argument.
- :param str keyword: the keyword of the argument.
- :returns: The node representing the argument, None if the argument is not found.
- :rtype: nodes.Name
- :raises ValueError: if both position and keyword are None.
- :raises NoSuchArgumentError: if no argument at the provided position or with
- the provided keyword.
- """
- if position is None and keyword is None:
- raise ValueError("Must specify at least one of: position or keyword.")
- if position is not None:
- try:
- return call_node.args[position]
- except IndexError:
- pass
- if keyword and call_node.keywords:
- for arg in call_node.keywords:
- if arg.arg == keyword:
- return arg.value
- raise NoSuchArgumentError
- def infer_kwarg_from_call(call_node: nodes.Call, keyword: str) -> nodes.Name | None:
- """Returns the specified argument from a function's kwargs.
- :param nodes.Call call_node: Node representing a function call to check.
- :param str keyword: Name of the argument to be extracted.
- :returns: The node representing the argument, None if the argument is not found.
- :rtype: nodes.Name
- """
- for arg in call_node.kwargs:
- inferred = safe_infer(arg.value)
- if isinstance(inferred, nodes.Dict):
- for item in inferred.items:
- if item[0].value == keyword:
- return item[1]
- return None
- def inherit_from_std_ex(node: nodes.NodeNG | astroid.Instance) -> bool:
- """Return whether the given class node is subclass of
- exceptions.Exception.
- """
- ancestors = node.ancestors() if hasattr(node, "ancestors") else []
- return any(
- ancestor.name in {"Exception", "BaseException"}
- and ancestor.root().name == EXCEPTIONS_MODULE
- for ancestor in itertools.chain([node], ancestors)
- )
- def error_of_type(
- handler: nodes.ExceptHandler,
- error_type: str | type[Exception] | tuple[str | type[Exception], ...],
- ) -> bool:
- """Check if the given exception handler catches
- the given error_type.
- The *handler* parameter is a node, representing an ExceptHandler node.
- The *error_type* can be an exception, such as AttributeError,
- the name of an exception, or it can be a tuple of errors.
- The function will return True if the handler catches any of the
- given errors.
- """
- def stringify_error(error: str | type[Exception]) -> str:
- if not isinstance(error, str):
- return error.__name__
- return error
- if not isinstance(error_type, tuple):
- error_type = (error_type,)
- expected_errors = {stringify_error(error) for error in error_type}
- if not handler.type:
- return False
- return handler.catch(expected_errors) # type: ignore[no-any-return]
- def decorated_with_property(node: nodes.FunctionDef) -> bool:
- """Detect if the given function node is decorated with a property."""
- if not node.decorators:
- return False
- for decorator in node.decorators.nodes:
- try:
- if _is_property_decorator(decorator):
- return True
- except astroid.InferenceError:
- pass
- return False
- def _is_property_kind(node: nodes.NodeNG, *kinds: str) -> bool:
- if not isinstance(node, (astroid.UnboundMethod, nodes.FunctionDef)):
- return False
- if node.decorators:
- for decorator in node.decorators.nodes:
- if isinstance(decorator, nodes.Attribute) and decorator.attrname in kinds:
- return True
- return False
- def is_property_setter(node: nodes.NodeNG) -> bool:
- """Check if the given node is a property setter."""
- return _is_property_kind(node, "setter")
- def is_property_deleter(node: nodes.NodeNG) -> bool:
- """Check if the given node is a property deleter."""
- return _is_property_kind(node, "deleter")
- def is_property_setter_or_deleter(node: nodes.NodeNG) -> bool:
- """Check if the given node is either a property setter or a deleter."""
- return _is_property_kind(node, "setter", "deleter")
- def _is_property_decorator(decorator: nodes.Name) -> bool:
- for inferred in decorator.infer():
- if isinstance(inferred, nodes.ClassDef):
- if inferred.qname() in {"builtins.property", "functools.cached_property"}:
- return True
- for ancestor in inferred.ancestors():
- if ancestor.name == "property" and ancestor.root().name == "builtins":
- return True
- elif isinstance(inferred, nodes.FunctionDef):
- # If decorator is function, check if it has exactly one return
- # and the return is itself a function decorated with property
- returns: list[nodes.Return] = list(
- inferred._get_return_nodes_skip_functions()
- )
- if len(returns) == 1 and isinstance(
- returns[0].value, (nodes.Name, nodes.Attribute)
- ):
- inferred = safe_infer(returns[0].value)
- if (
- inferred
- and isinstance(inferred, objects.Property)
- and isinstance(inferred.function, nodes.FunctionDef)
- ):
- return decorated_with_property(inferred.function)
- return False
- def decorated_with(
- func: (
- nodes.ClassDef | nodes.FunctionDef | astroid.BoundMethod | astroid.UnboundMethod
- ),
- qnames: Iterable[str],
- ) -> bool:
- """Determine if the `func` node has a decorator with the qualified name `qname`."""
- decorators = func.decorators.nodes if func.decorators else []
- for decorator_node in decorators:
- if isinstance(decorator_node, nodes.Call):
- # We only want to infer the function name
- decorator_node = decorator_node.func
- try:
- if any(
- i.name in qnames or i.qname() in qnames
- for i in decorator_node.infer()
- if isinstance(i, (nodes.ClassDef, nodes.FunctionDef))
- ):
- return True
- except astroid.InferenceError:
- continue
- return False
- def uninferable_final_decorators(
- node: nodes.Decorators,
- ) -> list[nodes.Attribute | nodes.Name | None]:
- """Return a list of uninferable `typing.final` decorators in `node`.
- This function is used to determine if the `typing.final` decorator is used
- with an unsupported Python version; the decorator cannot be inferred when
- using a Python version lower than 3.8.
- """
- decorators = []
- for decorator in getattr(node, "nodes", []):
- import_nodes: tuple[nodes.Import | nodes.ImportFrom] | None = None
- # Get the `Import` node. The decorator is of the form: @module.name
- if isinstance(decorator, nodes.Attribute):
- inferred = safe_infer(decorator.expr)
- if isinstance(inferred, nodes.Module) and inferred.qname() == "typing":
- _, import_nodes = decorator.expr.lookup(decorator.expr.name)
- # Get the `ImportFrom` node. The decorator is of the form: @name
- elif isinstance(decorator, nodes.Name):
- _, import_nodes = decorator.lookup(decorator.name)
- # The `final` decorator is expected to be found in the
- # import_nodes. Continue if we don't find any `Import` or `ImportFrom`
- # nodes for this decorator.
- if not import_nodes:
- continue
- import_node = import_nodes[0]
- if not isinstance(import_node, (nodes.Import, nodes.ImportFrom)):
- continue
- import_names = dict(import_node.names)
- # Check if the import is of the form: `from typing import final`
- is_from_import = ("final" in import_names) and import_node.modname == "typing"
- # Check if the import is of the form: `import typing`
- is_import = ("typing" in import_names) and getattr(
- decorator, "attrname", None
- ) == "final"
- if is_from_import or is_import:
- inferred = safe_infer(decorator)
- if inferred is None or isinstance(inferred, util.UninferableBase):
- decorators.append(decorator)
- return decorators
- @lru_cache(maxsize=1024)
- def unimplemented_abstract_methods(
- node: nodes.ClassDef, is_abstract_cb: nodes.FunctionDef | None = None
- ) -> dict[str, nodes.FunctionDef]:
- """Get the unimplemented abstract methods for the given *node*.
- A method can be considered abstract if the callback *is_abstract_cb*
- returns a ``True`` value. The check defaults to verifying that
- a method is decorated with abstract methods.
- It will return a dictionary of abstract method
- names and their inferred objects.
- """
- if is_abstract_cb is None:
- is_abstract_cb = partial(decorated_with, qnames=ABC_METHODS)
- visited: dict[str, nodes.FunctionDef] = {}
- try:
- mro = reversed(node.mro())
- except astroid.ResolveError:
- # Probably inconsistent hierarchy, don't try to figure this out here.
- return {}
- for ancestor in mro:
- for obj in ancestor.values():
- inferred = obj
- if isinstance(obj, nodes.AssignName):
- inferred = safe_infer(obj)
- if not inferred:
- # Might be an abstract function,
- # but since we don't have enough information
- # in order to take this decision, we're taking
- # the *safe* decision instead.
- if obj.name in visited:
- del visited[obj.name]
- continue
- if not isinstance(inferred, nodes.FunctionDef):
- if obj.name in visited:
- del visited[obj.name]
- if isinstance(inferred, nodes.FunctionDef):
- # It's critical to use the original name,
- # since after inferring, an object can be something
- # else than expected, as in the case of the
- # following assignment.
- #
- # class A:
- # def keys(self): pass
- # __iter__ = keys
- abstract = is_abstract_cb(inferred)
- if abstract:
- visited[obj.name] = inferred
- elif not abstract and obj.name in visited:
- del visited[obj.name]
- return visited
- def find_try_except_wrapper_node(
- node: nodes.NodeNG,
- ) -> nodes.ExceptHandler | nodes.Try | None:
- """Return the ExceptHandler or the Try node in which the node is."""
- current = node
- ignores = (nodes.ExceptHandler, nodes.Try)
- while current and not isinstance(current.parent, ignores):
- current = current.parent
- if current and isinstance(current.parent, ignores):
- return current.parent
- return None
- def find_except_wrapper_node_in_scope(
- node: nodes.NodeNG,
- ) -> nodes.ExceptHandler | None:
- """Return the ExceptHandler in which the node is, without going out of scope."""
- for current in node.node_ancestors():
- match current:
- case nodes.LocalsDictNodeNG():
- # If we're inside a function/class definition, we don't want to keep checking
- # higher ancestors for `except` clauses, because if these exist, it means our
- # function/class was defined in an `except` clause, rather than the current code
- # actually running in an `except` clause.
- return None
- case nodes.ExceptHandler():
- return current
- return None
- def is_from_fallback_block(node: nodes.NodeNG) -> bool:
- """Check if the given node is from a fallback import block."""
- context = find_try_except_wrapper_node(node)
- if not context:
- return False
- if isinstance(context, nodes.ExceptHandler):
- other_body = context.parent.body
- handlers = context.parent.handlers
- else:
- other_body = itertools.chain.from_iterable(
- handler.body for handler in context.handlers
- )
- handlers = context.handlers
- has_fallback_imports = any(
- isinstance(import_node, (nodes.ImportFrom, nodes.Import))
- for import_node in other_body
- )
- ignores_import_error = _except_handlers_ignores_exceptions(
- handlers, (ImportError, ModuleNotFoundError)
- )
- return ignores_import_error or has_fallback_imports
- def _except_handlers_ignores_exceptions(
- handlers: nodes.ExceptHandler,
- exceptions: tuple[type[ImportError], type[ModuleNotFoundError]],
- ) -> bool:
- func = partial(error_of_type, error_type=exceptions)
- return any(func(handler) for handler in handlers)
- def get_exception_handlers(
- node: nodes.NodeNG, exception: type[Exception] | str = Exception
- ) -> list[nodes.ExceptHandler] | None:
- """Return the collections of handlers handling the exception in arguments.
- Args:
- node (nodes.NodeNG): A node that is potentially wrapped in a try except.
- exception (builtin.Exception or str): exception or name of the exception.
- Returns:
- list: the collection of handlers that are handling the exception or None.
- """
- context = find_try_except_wrapper_node(node)
- if isinstance(context, nodes.Try):
- return [
- handler for handler in context.handlers if error_of_type(handler, exception)
- ]
- return []
- def get_contextlib_with_statements(node: nodes.NodeNG) -> Iterator[nodes.With]:
- """Get all contextlib.with statements in the ancestors of the given node."""
- for with_node in node.node_ancestors():
- if isinstance(with_node, nodes.With):
- yield with_node
- def _suppresses_exception(
- call: nodes.Call, exception: type[Exception] | str = Exception
- ) -> bool:
- """Check if the given node suppresses the given exception."""
- if not isinstance(exception, str):
- exception = exception.__name__
- for arg in call.args:
- match inferred := safe_infer(arg):
- case nodes.ClassDef():
- if inferred.name == exception:
- return True
- case nodes.Tuple():
- for elt in inferred.elts:
- inferred_elt = safe_infer(elt)
- if (
- isinstance(inferred_elt, nodes.ClassDef)
- and inferred_elt.name == exception
- ):
- return True
- return False
- def get_contextlib_suppressors(
- node: nodes.NodeNG, exception: type[Exception] | str = Exception
- ) -> Iterator[nodes.With]:
- """Return the contextlib suppressors handling the exception.
- Args:
- node (nodes.NodeNG): A node that is potentially wrapped in a contextlib.suppress.
- exception (builtin.Exception): exception or name of the exception.
- Yields:
- nodes.With: A with node that is suppressing the exception.
- """
- for with_node in get_contextlib_with_statements(node):
- for item, _ in with_node.items:
- if isinstance(item, nodes.Call):
- inferred = safe_infer(item.func)
- if (
- isinstance(inferred, nodes.ClassDef)
- and inferred.qname() == "contextlib.suppress"
- ):
- if _suppresses_exception(item, exception):
- yield with_node
- def is_node_inside_try_except(node: nodes.Raise) -> bool:
- """Check if the node is directly under a Try/Except statement
- (but not under an ExceptHandler!).
- Args:
- node (nodes.Raise): the node raising the exception.
- Returns:
- bool: True if the node is inside a try/except statement, False otherwise.
- """
- context = find_try_except_wrapper_node(node)
- return isinstance(context, nodes.Try)
- def node_ignores_exception(
- node: nodes.NodeNG, exception: type[Exception] | str = Exception
- ) -> bool:
- """Check if the node is in a Try which handles the given exception.
- If the exception is not given, the function is going to look for bare
- excepts.
- """
- managing_handlers = get_exception_handlers(node, exception)
- if managing_handlers:
- return True
- return any(get_contextlib_suppressors(node, exception))
- @lru_cache(maxsize=1024)
- def class_is_abstract(node: nodes.ClassDef) -> bool:
- """Return true if the given class node should be considered as an abstract
- class.
- """
- # Protocol classes are considered "abstract"
- if is_protocol_class(node):
- return True
- # Only check for explicit metaclass=ABCMeta on this specific class
- meta = node.declared_metaclass()
- if meta is not None:
- if meta.name == "ABCMeta" and meta.root().name in ABC_MODULES:
- return True
- for ancestor in node.ancestors():
- if ancestor.name == "ABC" and ancestor.root().name in ABC_MODULES:
- # abc.ABC inheritance
- return True
- for method in node.methods():
- if method.parent.frame() is node:
- if method.is_abstract(pass_is_abstract=False):
- return True
- return False
- def _supports_protocol_method(value: nodes.NodeNG, attr: str) -> bool:
- try:
- attributes = value.getattr(attr)
- except astroid.NotFoundError:
- return False
- first = attributes[0]
- # Return False if a constant is assigned
- if isinstance(first, nodes.AssignName):
- this_assign_parent = get_node_first_ancestor_of_type(
- first, (nodes.Assign, nodes.NamedExpr)
- )
- if this_assign_parent is None: # pragma: no cover
- # Cannot imagine this being None, but return True to avoid false positives
- return True
- if isinstance(this_assign_parent.value, nodes.BaseContainer):
- if all(isinstance(n, nodes.Const) for n in this_assign_parent.value.elts):
- return False
- if isinstance(this_assign_parent.value, nodes.Const):
- return False
- return True
- def is_comprehension(node: nodes.NodeNG) -> bool:
- comprehensions = (
- nodes.ListComp,
- nodes.SetComp,
- nodes.DictComp,
- nodes.GeneratorExp,
- )
- return isinstance(node, comprehensions)
- def _supports_mapping_protocol(value: nodes.NodeNG) -> bool:
- return _supports_protocol_method(
- value, GETITEM_METHOD
- ) and _supports_protocol_method(value, KEYS_METHOD)
- def _supports_membership_test_protocol(value: nodes.NodeNG) -> bool:
- return _supports_protocol_method(value, CONTAINS_METHOD)
- def _supports_iteration_protocol(value: nodes.NodeNG) -> bool:
- return _supports_protocol_method(value, ITER_METHOD) or _supports_protocol_method(
- value, GETITEM_METHOD
- )
- def _supports_async_iteration_protocol(value: nodes.NodeNG) -> bool:
- return _supports_protocol_method(value, AITER_METHOD)
- def _supports_getitem_protocol(value: nodes.NodeNG) -> bool:
- return _supports_protocol_method(value, GETITEM_METHOD)
- def _supports_setitem_protocol(value: nodes.NodeNG) -> bool:
- return _supports_protocol_method(value, SETITEM_METHOD)
- def _supports_delitem_protocol(value: nodes.NodeNG) -> bool:
- return _supports_protocol_method(value, DELITEM_METHOD)
- def _is_abstract_class_name(name: str) -> bool:
- lname = name.lower()
- is_mixin = lname.endswith("mixin")
- is_abstract = lname.startswith("abstract")
- is_base = lname.startswith("base") or lname.endswith("base")
- return is_mixin or is_abstract or is_base
- def is_inside_abstract_class(node: nodes.NodeNG) -> bool:
- while node is not None:
- if isinstance(node, nodes.ClassDef):
- if class_is_abstract(node):
- return True
- name = getattr(node, "name", None)
- if name is not None and _is_abstract_class_name(name):
- return True
- node = node.parent
- return False
- def _supports_protocol(
- value: nodes.NodeNG, protocol_callback: Callable[[nodes.NodeNG], bool]
- ) -> bool:
- match value:
- case nodes.ClassDef():
- if not has_known_bases(value):
- return True
- # classobj can only be iterable if it has an iterable metaclass
- meta = value.metaclass()
- if meta is not None:
- if protocol_callback(meta):
- return True
- case astroid.BaseInstance():
- if not has_known_bases(value):
- return True
- if value.has_dynamic_getattr():
- return True
- if protocol_callback(value):
- return True
- case nodes.ComprehensionScope():
- return True
- case bases.Proxy(_proxied=astroid.BaseInstance() as p) if has_known_bases(p):
- return protocol_callback(p)
- return False
- def is_iterable(value: nodes.NodeNG, check_async: bool = False) -> bool:
- if check_async:
- protocol_check = _supports_async_iteration_protocol
- else:
- protocol_check = _supports_iteration_protocol
- return _supports_protocol(value, protocol_check)
- def is_mapping(value: nodes.NodeNG) -> bool:
- return _supports_protocol(value, _supports_mapping_protocol)
- def supports_membership_test(value: nodes.NodeNG) -> bool:
- supported = _supports_protocol(value, _supports_membership_test_protocol)
- return supported or is_iterable(value)
- def supports_getitem(value: nodes.NodeNG, node: nodes.NodeNG) -> bool:
- if isinstance(value, nodes.ClassDef):
- if _supports_protocol_method(value, CLASS_GETITEM_METHOD):
- return True
- if is_postponed_evaluation_enabled(node) and is_node_in_type_annotation_context(
- node
- ):
- return True
- return _supports_protocol(value, _supports_getitem_protocol)
- def supports_setitem(value: nodes.NodeNG, _: nodes.NodeNG) -> bool:
- return _supports_protocol(value, _supports_setitem_protocol)
- def supports_delitem(value: nodes.NodeNG, _: nodes.NodeNG) -> bool:
- return _supports_protocol(value, _supports_delitem_protocol)
- def _get_python_type_of_node(node: nodes.NodeNG) -> str | None:
- pytype: Callable[[], str] | None = getattr(node, "pytype", None)
- if callable(pytype):
- return pytype()
- return None
- @lru_cache(maxsize=1024)
- def safe_infer(
- node: nodes.NodeNG,
- context: InferenceContext | None = None,
- *,
- compare_constants: bool = False,
- compare_constructors: bool = False,
- ) -> InferenceResult | None:
- """Return the inferred value for the given node.
- Return None if inference failed or if there is some ambiguity (more than
- one node has been inferred of different types).
- If compare_constants is True and if multiple constants are inferred,
- unequal inferred values are also considered ambiguous and return None.
- If compare_constructors is True and if multiple classes are inferred,
- constructors with different signatures are held ambiguous and return None.
- """
- inferred_types: set[str | None] = set()
- try:
- infer_gen = node.infer(context=context)
- value = next(infer_gen)
- except astroid.InferenceError:
- return None
- except Exception as e: # pragma: no cover
- raise AstroidError from e
- if not isinstance(value, util.UninferableBase):
- inferred_types.add(_get_python_type_of_node(value))
- # pylint: disable = too-many-try-statements
- try:
- for inferred in infer_gen:
- inferred_type = _get_python_type_of_node(inferred)
- if inferred_type not in inferred_types:
- return None # If there is ambiguity on the inferred node.
- if (
- compare_constants
- and isinstance(inferred, nodes.Const)
- and isinstance(value, nodes.Const)
- and inferred.value != value.value
- ):
- return None
- if (
- isinstance(inferred, nodes.FunctionDef)
- and isinstance(value, nodes.FunctionDef)
- and function_arguments_are_ambiguous(inferred, value)
- ):
- return None
- if (
- compare_constructors
- and isinstance(inferred, nodes.ClassDef)
- and isinstance(value, nodes.ClassDef)
- and class_constructors_are_ambiguous(inferred, value)
- ):
- return None
- except astroid.InferenceError:
- return None # There is some kind of ambiguity
- except StopIteration:
- return value
- except Exception as e: # pragma: no cover
- raise AstroidError from e
- return value if len(inferred_types) <= 1 else None
- @lru_cache(maxsize=512)
- def infer_all(
- node: nodes.NodeNG, context: InferenceContext | None = None
- ) -> list[InferenceResult]:
- try:
- return list(node.infer(context=context))
- except astroid.InferenceError:
- return []
- except Exception as e: # pragma: no cover
- raise AstroidError from e
- def function_arguments_are_ambiguous(
- func1: nodes.FunctionDef, func2: nodes.FunctionDef
- ) -> bool:
- if func1.argnames() != func2.argnames():
- return True
- # Check ambiguity among function default values
- pairs_of_defaults = [
- (func1.args.defaults, func2.args.defaults),
- (func1.args.kw_defaults, func2.args.kw_defaults),
- ]
- for zippable_default in pairs_of_defaults:
- if None in zippable_default:
- continue
- if len(zippable_default[0]) != len(zippable_default[1]):
- return True
- for default1, default2 in zip(*zippable_default):
- match (default1, default2):
- case [nodes.Const(), nodes.Const()]:
- return default1.value != default2.value # type: ignore[no-any-return]
- case [nodes.Name(), nodes.Name()]:
- return default1.name != default2.name # type: ignore[no-any-return]
- case _:
- return True
- return False
- def class_constructors_are_ambiguous(
- class1: nodes.ClassDef, class2: nodes.ClassDef
- ) -> bool:
- try:
- constructor1 = class1.local_attr("__init__")[0]
- constructor2 = class2.local_attr("__init__")[0]
- except astroid.NotFoundError:
- return False
- if not isinstance(constructor1, nodes.FunctionDef):
- return False
- if not isinstance(constructor2, nodes.FunctionDef):
- return False
- return function_arguments_are_ambiguous(constructor1, constructor2)
- def has_known_bases(
- klass: nodes.ClassDef, context: InferenceContext | None = None
- ) -> bool:
- """Return true if all base classes of a class could be inferred."""
- try:
- return klass._all_bases_known # type: ignore[no-any-return]
- except AttributeError:
- pass
- for base in klass.bases:
- result = safe_infer(base, context=context)
- if (
- not isinstance(result, nodes.ClassDef)
- or result is klass
- or not has_known_bases(result, context=context)
- ):
- klass._all_bases_known = False
- return False
- klass._all_bases_known = True
- return True
- def is_none(node: nodes.NodeNG) -> bool:
- match node:
- case None | nodes.Const(value=None) | nodes.Name(value="None"):
- return True
- return False
- def node_type(node: nodes.NodeNG) -> SuccessfulInferenceResult | None:
- """Return the inferred type for `node`.
- If there is more than one possible type, or if inferred type is Uninferable or None,
- return None
- """
- # check there is only one possible type for the assign node. Else we
- # don't handle it for now
- types: set[SuccessfulInferenceResult] = set()
- try:
- for var_type in node.infer():
- if isinstance(var_type, util.UninferableBase) or is_none(var_type):
- continue
- types.add(var_type)
- if len(types) > 1:
- return None
- except astroid.InferenceError:
- return None
- return types.pop() if types else None
- def is_registered_in_singledispatch_function(node: nodes.FunctionDef) -> bool:
- """Check if the given function node is a singledispatch function."""
- singledispatch_qnames = (
- "functools.singledispatch",
- "singledispatch.singledispatch",
- )
- if not isinstance(node, nodes.FunctionDef):
- return False
- decorators = node.decorators.nodes if node.decorators else []
- for decorator in decorators:
- # func.register are function calls or register attributes
- # when the function is annotated with types
- match decorator:
- case nodes.Call(func=func) | (nodes.Attribute() as func):
- pass
- case _:
- continue
- if not (isinstance(func, nodes.Attribute) and func.attrname == "register"):
- continue
- try:
- func_def = next(func.expr.infer())
- except astroid.InferenceError:
- continue
- if isinstance(func_def, nodes.FunctionDef):
- return decorated_with(func_def, singledispatch_qnames)
- return False
- def find_inferred_fn_from_register(node: nodes.NodeNG) -> nodes.FunctionDef | None:
- # func.register are function calls or register attributes
- # when the function is annotated with types
- match node:
- case nodes.Call(func=func) | (nodes.Attribute() as func):
- pass
- case _:
- return None
- if not (isinstance(func, nodes.Attribute) and func.attrname == "register"):
- return None
- func_def = safe_infer(func.expr)
- if not isinstance(func_def, nodes.FunctionDef):
- return None
- return func_def
- def is_registered_in_singledispatchmethod_function(node: nodes.FunctionDef) -> bool:
- """Check if the given function node is a singledispatchmethod function."""
- singledispatchmethod_qnames = (
- "functools.singledispatchmethod",
- "singledispatch.singledispatchmethod",
- )
- decorators = node.decorators.nodes if node.decorators else []
- for decorator in decorators:
- func_def = find_inferred_fn_from_register(decorator)
- if func_def:
- return decorated_with(func_def, singledispatchmethod_qnames)
- return False
- def get_node_last_lineno(node: nodes.NodeNG) -> int:
- """Get the last lineno of the given node.
- For a simple statement this will just be node.lineno,
- but for a node that has child statements (e.g. a method) this will be the lineno of the last
- child statement recursively.
- """
- # 'finalbody' is always the last clause in a try statement, if present
- if getattr(node, "finalbody", False):
- return get_node_last_lineno(node.finalbody[-1])
- # For if, while, and for statements 'orelse' is always the last clause.
- # For try statements 'orelse' is the last in the absence of a 'finalbody'
- if getattr(node, "orelse", False):
- return get_node_last_lineno(node.orelse[-1])
- # try statements have the 'handlers' last if there is no 'orelse' or 'finalbody'
- if getattr(node, "handlers", False):
- return get_node_last_lineno(node.handlers[-1])
- # All compound statements have a 'body'
- if getattr(node, "body", False):
- return get_node_last_lineno(node.body[-1])
- # Not a compound statement
- return node.lineno # type: ignore[no-any-return]
- def is_postponed_evaluation_enabled(node: nodes.NodeNG) -> bool:
- """Check if the postponed evaluation of annotations is enabled."""
- module = node.root()
- return "annotations" in module.future_imports
- def is_node_in_type_annotation_context(node: nodes.NodeNG) -> bool:
- """Check if node is in type annotation context.
- Check for 'AnnAssign', function 'Arguments',
- or part of function return type annotation.
- """
- current_node, parent_node = node, node.parent
- while True:
- match parent_node:
- case nodes.AnnAssign(annotation=ann) if ann == current_node:
- return True
- case nodes.Arguments() if current_node in (
- *parent_node.annotations,
- *parent_node.posonlyargs_annotations,
- *parent_node.kwonlyargs_annotations,
- parent_node.varargannotation,
- parent_node.kwargannotation,
- ):
- return True
- case nodes.FunctionDef(returns=ret) if ret == current_node:
- return True
- current_node, parent_node = parent_node, parent_node.parent
- if isinstance(parent_node, nodes.Module):
- return False
- def is_node_in_pep695_type_context(node: nodes.NodeNG) -> nodes.NodeNG | None:
- """Check if node is used in a TypeAlias or as part of a type param."""
- return get_node_first_ancestor_of_type(
- node, (nodes.TypeAlias, nodes.TypeVar, nodes.ParamSpec, nodes.TypeVarTuple)
- )
- def is_subclass_of(child: nodes.ClassDef, parent: nodes.ClassDef) -> bool:
- """Check if first node is a subclass of second node.
- :param child: Node to check for subclass.
- :param parent: Node to check for superclass.
- :returns: True if child is derived from parent. False otherwise.
- """
- if not all(isinstance(node, nodes.ClassDef) for node in (child, parent)):
- return False
- for ancestor in child.ancestors():
- try:
- if astroid.helpers.is_subtype(ancestor, parent):
- return True
- except astroid.exceptions._NonDeducibleTypeHierarchy:
- continue
- return False
- @lru_cache(maxsize=1024)
- def is_overload_stub(node: nodes.NodeNG) -> bool:
- """Check if a node is a function stub decorated with typing.overload.
- :param node: Node to check.
- :returns: True if node is an overload function stub. False otherwise.
- """
- decorators = getattr(node, "decorators", None)
- return bool(decorators and decorated_with(node, ["typing.overload", "overload"]))
- def is_protocol_class(cls: nodes.NodeNG) -> bool:
- """Check if the given node represents a protocol class.
- :param cls: The node to check
- :returns: True if the node is or inherits from typing.Protocol directly, false otherwise.
- """
- if not isinstance(cls, nodes.ClassDef):
- return False
- # Return if klass is protocol
- if cls.qname() in TYPING_PROTOCOLS:
- return True
- for base in cls.bases:
- try:
- for inf_base in base.infer():
- if inf_base.qname() in TYPING_PROTOCOLS:
- return True
- except astroid.InferenceError:
- continue
- return False
- def is_call_of_name(node: nodes.NodeNG, name: str) -> bool:
- """Checks if node is a function call with the given name."""
- match node:
- case nodes.Call(func=nodes.Name(name=func_name)):
- return func_name == name # type: ignore[no-any-return]
- return False
- def is_test_condition(
- node: nodes.NodeNG,
- parent: nodes.NodeNG | None = None,
- ) -> bool:
- """Returns true if the given node is being tested for truthiness."""
- match parent := parent or node.parent:
- case nodes.While() | nodes.If() | nodes.IfExp() | nodes.Assert():
- return node is parent.test or parent.test.parent_of(node)
- case nodes.Comprehension():
- return node in parent.ifs
- return is_call_of_name(parent, "bool") and parent.parent_of(node)
- def is_classdef_type(node: nodes.ClassDef) -> bool:
- """Test if ClassDef node is Type."""
- if node.name == "type":
- return True
- return any(isinstance(b, nodes.Name) and b.name == "type" for b in node.bases)
- def is_attribute_typed_annotation(
- node: nodes.ClassDef | astroid.Instance, attr_name: str
- ) -> bool:
- """Test if attribute is typed annotation in current node
- or any base nodes.
- """
- match node.locals.get(attr_name, [None])[0]:
- case nodes.AssignName(parent=nodes.AnnAssign()):
- return True
- for base in node.bases:
- match inferred := safe_infer(base):
- case nodes.ClassDef() if is_attribute_typed_annotation(inferred, attr_name):
- return True
- return False
- def is_enum(node: nodes.ClassDef) -> bool:
- return node.name == "Enum" and node.root().name == "enum" # type: ignore[no-any-return]
- def is_assign_name_annotated_with(node: nodes.AssignName, typing_name: str) -> bool:
- """Test if AssignName node has `typing_name` annotation.
- Especially useful to check for `typing._SpecialForm` instances
- like: `Union`, `Optional`, `Literal`, `ClassVar`, `Final`.
- """
- if not isinstance(node.parent, nodes.AnnAssign):
- return False
- annotation = node.parent.annotation
- if isinstance(annotation, nodes.Subscript):
- annotation = annotation.value
- match annotation:
- case nodes.Name(name=n) | nodes.Attribute(attrname=n) if n == typing_name:
- return True
- return False
- def is_assign_name_annotated_with_class_var_typing_name(
- node: nodes.AssignName, typing_name: str
- ) -> bool:
- if not is_assign_name_annotated_with(node, "ClassVar"):
- return False
- annotation = node.parent.annotation
- if isinstance(annotation, nodes.Subscript):
- annotation = annotation.slice
- if isinstance(annotation, nodes.Subscript):
- annotation = annotation.value
- match annotation:
- case nodes.Name(name=n) | nodes.Attribute(attrname=n) if n == typing_name:
- return True
- return False
- def get_iterating_dictionary_name(node: nodes.For | nodes.Comprehension) -> str | None:
- """Get the name of the dictionary which keys are being iterated over on
- a ``nodes.For`` or ``nodes.Comprehension`` node.
- If the iterating object is not either the keys method of a dictionary
- or a dictionary itself, this returns None.
- """
- # Is it a proper keys call?
- match node.iter:
- case nodes.Call(func=nodes.Attribute(attrname="keys")):
- inferred = safe_infer(node.iter.func)
- if not isinstance(inferred, astroid.BoundMethod):
- return None
- return node.iter.as_string().rpartition(".keys")[0] # type: ignore[no-any-return]
- # Is it a dictionary?
- if isinstance(node.iter, (nodes.Name, nodes.Attribute)):
- inferred = safe_infer(node.iter)
- if not isinstance(inferred, nodes.Dict):
- return None
- return node.iter.as_string() # type: ignore[no-any-return]
- return None
- def get_subscript_const_value(node: nodes.Subscript) -> nodes.Const:
- """Returns the value 'subscript.slice' of a Subscript node.
- :param node: Subscript Node to extract value from
- :returns: Const Node containing subscript value
- :raises InferredTypeError: if the subscript node cannot be inferred as a Const
- """
- inferred = safe_infer(node.slice)
- if not isinstance(inferred, nodes.Const):
- raise InferredTypeError("Subscript.slice cannot be inferred as a nodes.Const")
- return inferred
- def get_import_name(importnode: ImportNode, modname: str | None) -> str | None:
- """Get a prepared module name from the given import node.
- In the case of relative imports, this will return the
- absolute qualified module name, which might be useful
- for debugging. Otherwise, the initial module name
- is returned unchanged.
- :param importnode: node representing import statement.
- :param modname: module name from import statement.
- :returns: absolute qualified module name of the module
- used in import.
- """
- if isinstance(importnode, nodes.ImportFrom) and importnode.level:
- root = importnode.root()
- if isinstance(root, nodes.Module):
- try:
- return root.relative_to_absolute_name( # type: ignore[no-any-return]
- modname, level=importnode.level
- )
- except TooManyLevelsError:
- return modname
- return modname
- def is_sys_guard(node: nodes.If) -> bool:
- """Return True if IF stmt is a sys.version_info guard.
- >>> import sys
- >>> from typing import Literal
- """
- if isinstance(node.test, nodes.Compare):
- value = node.test.left
- if isinstance(value, nodes.Subscript):
- value = value.value
- if (
- isinstance(value, nodes.Attribute)
- and value.as_string() == "sys.version_info"
- ):
- return True
- elif isinstance(node.test, nodes.Attribute) and node.test.as_string() in {
- "six.PY2",
- "six.PY3",
- }:
- return True
- return False
- def _is_node_in_same_scope(
- candidate: nodes.NodeNG, node_scope: nodes.LocalsDictNodeNG
- ) -> bool:
- if isinstance(candidate, (nodes.ClassDef, nodes.FunctionDef)):
- return candidate.parent is not None and candidate.parent.scope() is node_scope
- return candidate.scope() is node_scope
- def _is_reassigned_relative_to_current(
- node: nodes.NodeNG, varname: str, before: bool
- ) -> bool:
- """Check if the given variable name is reassigned in the same scope relative to
- the current node.
- """
- node_scope = node.scope()
- node_lineno = node.lineno
- if node_lineno is None:
- return False
- for a in node_scope.nodes_of_class(
- (nodes.AssignName, nodes.ClassDef, nodes.FunctionDef)
- ):
- if a.name == varname and a.lineno is not None:
- if before:
- if a.lineno < node_lineno:
- if _is_node_in_same_scope(a, node_scope):
- return True
- elif a.lineno > node_lineno:
- if _is_node_in_same_scope(a, node_scope):
- return True
- return False
- def is_reassigned_before_current(node: nodes.NodeNG, varname: str) -> bool:
- """Check if the given variable name is reassigned in the same scope before the
- current node.
- """
- return _is_reassigned_relative_to_current(node, varname, before=True)
- def is_reassigned_after_current(node: nodes.NodeNG, varname: str) -> bool:
- """Check if the given variable name is reassigned in the same scope after the
- current node.
- """
- return _is_reassigned_relative_to_current(node, varname, before=False)
- def is_deleted_after_current(node: nodes.NodeNG, varname: str) -> bool:
- """Check if the given variable name is deleted in the same scope after the current
- node.
- """
- return any(
- getattr(target, "name", None) == varname and target.lineno > node.lineno
- for del_node in node.scope().nodes_of_class(nodes.Delete)
- for target in del_node.targets
- )
- def is_function_body_ellipsis(node: nodes.FunctionDef) -> bool:
- """Checks whether a function body only consists of a single Ellipsis."""
- match node.body:
- case [nodes.Expr(value=nodes.Const(value=value))]:
- return value is Ellipsis
- return False
- def is_base_container(node: nodes.NodeNG | None) -> bool:
- return isinstance(node, nodes.BaseContainer) and not node.elts
- def is_empty_dict_literal(node: nodes.NodeNG | None) -> bool:
- return isinstance(node, nodes.Dict) and not node.items
- def is_empty_str_literal(node: nodes.NodeNG | None) -> bool:
- return (
- isinstance(node, nodes.Const) and isinstance(node.value, str) and not node.value
- )
- def returns_bool(node: nodes.NodeNG) -> bool:
- """Returns true if a node is a nodes.Return that returns a constant boolean."""
- match node:
- case nodes.Return(value=nodes.Const(value=bool())):
- return True
- return False
- def assigned_bool(node: nodes.NodeNG) -> bool:
- """Returns true if a node is a nodes.Assign that returns a constant boolean."""
- match node:
- case nodes.Assign(value=nodes.Const(value=bool())):
- return True
- return False
- def get_node_first_ancestor_of_type(
- node: nodes.NodeNG, ancestor_type: type[_NodeT] | tuple[type[_NodeT], ...]
- ) -> _NodeT | None:
- """Return the first parent node that is any of the provided types (or None)."""
- for ancestor in node.node_ancestors():
- if isinstance(ancestor, ancestor_type):
- return ancestor # type: ignore[no-any-return]
- return None
- def get_node_first_ancestor_of_type_and_its_child(
- node: nodes.NodeNG, ancestor_type: type[_NodeT] | tuple[type[_NodeT], ...]
- ) -> tuple[None, None] | tuple[_NodeT, nodes.NodeNG]:
- """Modified version of get_node_first_ancestor_of_type to also return the
- descendant visited directly before reaching the sought ancestor.
- Useful for extracting whether a statement is guarded by a try, except, or finally
- when searching for a Try ancestor.
- """
- child = node
- for ancestor in node.node_ancestors():
- if isinstance(ancestor, ancestor_type):
- return (ancestor, child)
- child = ancestor
- return None, None
- def in_type_checking_block(node: nodes.NodeNG) -> bool:
- """Check if a node is guarded by a TYPE_CHECKING guard."""
- for ancestor in node.node_ancestors():
- if not isinstance(ancestor, nodes.If):
- continue
- if isinstance(ancestor.test, nodes.Name):
- if ancestor.test.name != "TYPE_CHECKING":
- continue
- lookup_result = ancestor.test.lookup(ancestor.test.name)[1]
- if not lookup_result:
- return False
- maybe_import_from = lookup_result[0]
- if (
- isinstance(maybe_import_from, nodes.ImportFrom)
- and maybe_import_from.modname == "typing"
- ):
- return True
- match safe_infer(ancestor.test):
- case nodes.Const(value=False):
- return True
- elif isinstance(ancestor.test, nodes.Attribute):
- if ancestor.test.attrname != "TYPE_CHECKING":
- continue
- match safe_infer(ancestor.test.expr):
- case nodes.Module(name="typing"):
- return True
- return False
- def is_typing_member(node: nodes.NodeNG, names_to_check: tuple[str, ...]) -> bool:
- """Check if `node` is a member of the `typing` module and has one of the names from
- `names_to_check`.
- """
- match node:
- case nodes.Name():
- try:
- import_from = node.lookup(node.name)[1][0]
- except IndexError:
- return False
- match import_from:
- case nodes.ImportFrom(modname="typing"):
- return import_from.real_name(node.name) in names_to_check
- return False
- case nodes.Attribute():
- match safe_infer(node.expr):
- case nodes.Module(name="typing"):
- return node.attrname in names_to_check
- return False
- return False
- @lru_cache
- def in_for_else_branch(parent: nodes.NodeNG, stmt: Statement) -> bool:
- """Returns True if stmt is inside the else branch for a parent For stmt."""
- return isinstance(parent, nodes.For) and any(
- else_stmt.parent_of(stmt) or else_stmt == stmt for else_stmt in parent.orelse
- )
- def find_assigned_names_recursive(
- target: nodes.AssignName | nodes.BaseContainer,
- ) -> Iterator[str]:
- """Yield the names of assignment targets, accounting for nested ones."""
- match target:
- case nodes.AssignName():
- if target.name is not None:
- yield target.name
- case nodes.BaseContainer():
- for elt in target.elts:
- yield from find_assigned_names_recursive(elt)
- def has_starred_node_recursive(
- node: nodes.For | nodes.Comprehension | nodes.Set | nodes.Starred,
- ) -> Iterator[bool]:
- """Yield ``True`` if a Starred node is found recursively."""
- match node:
- case nodes.Starred():
- yield True
- case nodes.Set():
- for elt in node.elts:
- yield from has_starred_node_recursive(elt)
- case nodes.For() | nodes.Comprehension():
- for elt in node.iter.elts:
- yield from has_starred_node_recursive(elt)
- def is_hashable(node: nodes.NodeNG) -> bool:
- """Return whether any inferred value of `node` is hashable.
- When finding ambiguity, return True.
- """
- # pylint: disable = too-many-try-statements
- try:
- for inferred in node.infer():
- if isinstance(inferred, (nodes.ClassDef, util.UninferableBase)):
- return True
- if not hasattr(inferred, "igetattr"):
- return True
- hash_fn = next(inferred.igetattr("__hash__"))
- if hash_fn.parent is inferred:
- return True
- if getattr(hash_fn, "value", True) is not None:
- return True
- return False
- except astroid.InferenceError:
- return True
- def subscript_chain_is_equal(left: nodes.Subscript, right: nodes.Subscript) -> bool:
- while isinstance(left, nodes.Subscript) and isinstance(right, nodes.Subscript):
- try:
- if (
- get_subscript_const_value(left).value
- != get_subscript_const_value(right).value
- ):
- return False
- left = left.value
- right = right.value
- except InferredTypeError:
- return False
- return left.as_string() == right.as_string() # type: ignore[no-any-return]
- def _is_target_name_in_binop_side(
- target: nodes.AssignName | nodes.AssignAttr, side: nodes.NodeNG | None
- ) -> bool:
- """Determine whether the target name-like node is referenced in the side node."""
- match (side, target):
- case [nodes.Name(), nodes.AssignName()]:
- return target.name == side.name # type: ignore[no-any-return]
- case [nodes.Attribute(), nodes.AssignAttr()]:
- return target.as_string() == side.as_string() # type: ignore[no-any-return]
- case [nodes.Subscript(), nodes.Subscript()]:
- return subscript_chain_is_equal(target, side)
- case _:
- return False
- def is_augmented_assign(node: nodes.Assign) -> tuple[bool, str]:
- """Determine if the node is assigning itself (with modifications) to itself.
- For example: x = 1 + x
- """
- if not isinstance(node.value, nodes.BinOp):
- return False, ""
- binop = node.value
- target = node.targets[0]
- if not isinstance(target, (nodes.AssignName, nodes.AssignAttr, nodes.Subscript)):
- return False, ""
- # We don't want to catch x = "1" + x or x = "%s" % x
- if isinstance(binop.left, nodes.Const) and isinstance(
- binop.left.value, (str, bytes)
- ):
- return False, ""
- # This could probably be improved but for now we disregard all assignments from calls
- if isinstance(binop.left, nodes.Call) or isinstance(binop.right, nodes.Call):
- return False, ""
- if _is_target_name_in_binop_side(target, binop.left):
- return True, binop.op
- if (
- # Unless an operator is commutative, we should not raise (i.e. x = 3/x)
- binop.op in COMMUTATIVE_OPERATORS
- and _is_target_name_in_binop_side(target, binop.right)
- ):
- if isinstance(binop.left, nodes.Const):
- # This bizarrely became necessary after an unrelated call to igetattr().
- # Seems like a code smell uncovered in #10212.
- # tuple(node.frame().igetattr(node.name))
- inferred_left = binop.left
- else:
- inferred_left = safe_infer(binop.left)
- match inferred_left:
- case nodes.Const(value=int()):
- return True, binop.op
- return False, ""
- return False, ""
- def _qualified_name_parts(qualified_module_name: str) -> list[str]:
- """Split the names of the given module into subparts.
- For example,
- _qualified_name_parts('pylint.checkers.ImportsChecker')
- returns
- ['pylint', 'pylint.checkers', 'pylint.checkers.ImportsChecker']
- """
- names = qualified_module_name.split(".")
- return [".".join(names[0 : i + 1]) for i in range(len(names))]
- def is_module_ignored(
- qualified_module_name: str, ignored_modules: Iterable[str]
- ) -> bool:
- ignored_modules = set(ignored_modules)
- for current_module in _qualified_name_parts(qualified_module_name):
- # Try to match the module name directly
- if current_module in ignored_modules:
- return True
- for ignore in ignored_modules:
- # Try to see if the ignores pattern match against the module name.
- if fnmatch.fnmatch(current_module, ignore):
- return True
- return False
- def is_singleton_const(node: nodes.NodeNG) -> bool:
- return isinstance(node, nodes.Const) and any(
- node.value is value for value in SINGLETON_VALUES
- )
- def is_terminating_func(node: nodes.Call) -> bool:
- """Detect call to exit(), quit(), os._exit(), sys.exit(), or
- functions annotated with `typing.NoReturn` or `typing.Never`.
- """
- if not isinstance(node.func, (nodes.Attribute, nodes.Name)) or isinstance(
- node.parent, nodes.Lambda
- ):
- return False
- try:
- for inferred in node.func.infer():
- if (
- hasattr(inferred, "qname")
- and inferred.qname() in TERMINATING_FUNCS_QNAMES
- ):
- return True
- match inferred:
- case astroid.BoundMethod(_proxied=astroid.UnboundMethod(_proxied=p)):
- # Unwrap to get the actual function node object
- inferred = p
- if ( # pylint: disable=too-many-boolean-expressions
- isinstance(inferred, nodes.FunctionDef)
- and (
- not isinstance(inferred, nodes.AsyncFunctionDef)
- or isinstance(node.parent, nodes.Await)
- )
- and isinstance(inferred.returns, nodes.Name)
- and (inferred_func := safe_infer(inferred.returns))
- and hasattr(inferred_func, "qname")
- and inferred_func.qname()
- in (
- *TYPING_NEVER,
- *TYPING_NORETURN,
- # In Python 3.7 - 3.8, NoReturn is alias of '_SpecialForm'
- # "typing._SpecialForm",
- # But 'typing.Any' also inherits _SpecialForm
- # See #9751
- )
- ):
- return True
- except (StopIteration, astroid.InferenceError):
- pass
- return False
- def is_class_attr(name: str, klass: nodes.ClassDef) -> bool:
- try:
- klass.getattr(name)
- return True
- except astroid.NotFoundError:
- return False
- def get_inverse_comparator(op: str) -> str:
- """Returns the inverse comparator given a comparator.
- E.g. when given "==", returns "!="
- :param str op: the comparator to look up.
- :returns: The inverse of the comparator in string format
- :raises KeyError: if input is not recognized as a comparator
- """
- return {
- "==": "!=",
- "!=": "==",
- "<": ">=",
- ">": "<=",
- "<=": ">",
- ">=": "<",
- "in": "not in",
- "not in": "in",
- "is": "is not",
- "is not": "is",
- }[op]
- def not_condition_as_string(
- test_node: nodes.Compare | nodes.Name | nodes.UnaryOp | nodes.BoolOp | nodes.BinOp,
- ) -> str:
- match test_node:
- case nodes.UnaryOp():
- return test_node.operand.as_string() # type: ignore[no-any-return]
- case nodes.BoolOp():
- return f"not ({test_node.as_string()})"
- case nodes.Compare():
- lhs = test_node.left
- ops, rhs = test_node.ops[0]
- lower_priority_expressions = (
- nodes.Lambda,
- nodes.UnaryOp,
- nodes.BoolOp,
- nodes.IfExp,
- nodes.NamedExpr,
- )
- lhs = (
- f"({lhs.as_string()})"
- if isinstance(lhs, lower_priority_expressions)
- else lhs.as_string()
- )
- rhs = (
- f"({rhs.as_string()})"
- if isinstance(rhs, lower_priority_expressions)
- else rhs.as_string()
- )
- return f"{lhs} {get_inverse_comparator(ops)} {rhs}"
- case _:
- return f"not {test_node.as_string()}"
- @lru_cache(maxsize=1000)
- def overridden_method(
- klass: nodes.LocalsDictNodeNG, name: str | None
- ) -> nodes.FunctionDef | None:
- """Get overridden method if any."""
- try:
- parent = next(klass.local_attr_ancestors(name))
- except (StopIteration, KeyError):
- return None
- try:
- meth_node = parent[name]
- except KeyError: # pragma: no cover
- # We have found an ancestor defining <name> but it's not in the local
- # dictionary. This may happen with astroid built from living objects.
- return None
- if isinstance(meth_node, nodes.FunctionDef):
- return meth_node
- return None # pragma: no cover
- def is_enum_member(node: nodes.AssignName) -> bool:
- """Return `True` if `node` is an Enum member (is an item of the
- `__members__` container).
- """
- frame = node.frame()
- if (
- not isinstance(frame, nodes.ClassDef)
- or not frame.is_subtype_of("enum.Enum")
- or frame.root().qname() == "enum"
- ):
- return False
- members = frame.locals.get("__members__")
- # A dataclass is one known case for when `members` can be `None`
- if members is None:
- return False
- return node.name in [name_obj.name for value, name_obj in members[0].items]
|