blob: 0fd3a2b63797b842cfd9b276d4e32929da716952 [file] [edit]
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
"""Tests for specific behaviour of astroid nodes."""
from __future__ import annotations
import copy
import inspect
import os
import random
import sys
import textwrap
import unittest
import unittest.mock
import warnings
from typing import Any
import pytest
import astroid
from astroid import (
Uninferable,
bases,
builder,
extract_node,
nodes,
parse,
transforms,
util,
)
from astroid.const import (
IS_PYPY,
PY311_PLUS,
PY312_PLUS,
PY313_PLUS,
PY314_PLUS,
PY315_PLUS,
Context,
)
from astroid.context import InferenceContext
from astroid.exceptions import (
AstroidBuildingError,
AstroidSyntaxError,
AstroidTypeError,
AttributeInferenceError,
StatementMissing,
)
from astroid.nodes.node_classes import UNATTACHED_UNKNOWN
from astroid.nodes.scoped_nodes import SYNTHETIC_ROOT
from tests.testdata.python3.recursion_error import LONG_CHAINED_METHOD_CALL
from . import resources
abuilder = builder.AstroidBuilder(astroid.MANAGER)
class AsStringTest(resources.SysPathSetup, unittest.TestCase):
def test_tuple_as_string(self) -> None:
def build(string: str) -> nodes.Tuple:
return abuilder.string_build(string).body[0].value
self.assertEqual(build("1,").as_string(), "(1, )")
self.assertEqual(build("1, 2, 3").as_string(), "(1, 2, 3)")
self.assertEqual(build("(1, )").as_string(), "(1, )")
self.assertEqual(build("1, 2, 3").as_string(), "(1, 2, 3)")
def test_func_signature_issue_185(self) -> None:
code = textwrap.dedent("""
def test(a, b, c=42, *, x=42, **kwargs):
print(a, b, c, args)
""")
node = parse(code)
self.assertEqual(node.as_string().strip(), code.strip())
def test_as_string_for_list_containing_uninferable(self) -> None:
node = builder.extract_node("""
def foo():
bar = [arg] * 1
""")
binop = node.body[0].value
inferred = next(binop.infer())
self.assertEqual(inferred.as_string(), "[Uninferable]")
self.assertEqual(binop.as_string(), "[arg] * 1")
def test_frozenset_as_string(self) -> None:
ast_nodes = builder.extract_node("""
frozenset((1, 2, 3)) #@
frozenset({1, 2, 3}) #@
frozenset([1, 2, 3,]) #@
frozenset(None) #@
frozenset(1) #@
""")
ast_nodes = [next(node.infer()) for node in ast_nodes]
assert isinstance(ast_nodes, list)
self.assertEqual(ast_nodes[0].as_string(), "frozenset((1, 2, 3))")
self.assertEqual(ast_nodes[1].as_string(), "frozenset({1, 2, 3})")
self.assertEqual(ast_nodes[2].as_string(), "frozenset([1, 2, 3])")
self.assertNotEqual(ast_nodes[3].as_string(), "frozenset(None)")
self.assertNotEqual(ast_nodes[4].as_string(), "frozenset(1)")
def test_varargs_kwargs_as_string(self) -> None:
ast = abuilder.string_build("raise_string(*args, **kwargs)").body[0]
self.assertEqual(ast.as_string(), "raise_string(*args, **kwargs)")
@pytest.mark.skipif(PY314_PLUS, reason="return in finally is now a syntax error")
def test_module_as_string_pre_3_14(self) -> None:
"""Check as_string on a whole module prepared to be returned identically for py < 3.14."""
self.maxDiff = None
module = resources.build_file("data/module.py", "data.module")
with open(resources.find("data/module.py"), encoding="utf-8") as fobj:
# Ignore comments in python file
data_str = "\n".join(
[s for s in fobj.read().split("\n") if not s.lstrip().startswith("# ")]
)
self.assertMultiLineEqual(module.as_string(), data_str)
@pytest.mark.skipif(
not PY314_PLUS, reason="return in finally is now a syntax error"
)
def test_module_as_string(self) -> None:
"""Check as_string on a whole module prepared to be returned identically for py > 3.14."""
self.maxDiff = None
module = resources.build_file("data/module3.14.py", "data.module3.14")
with open(resources.find("data/module3.14.py"), encoding="utf-8") as fobj:
# Ignore comments in python file
data_str = "\n".join(
[s for s in fobj.read().split("\n") if not s.lstrip().startswith("# ")]
)
self.assertMultiLineEqual(module.as_string(), data_str)
def test_module2_as_string(self) -> None:
"""Check as_string on a whole module prepared to be returned identically."""
module2 = resources.build_file("data/module2.py", "data.module2")
with open(resources.find("data/module2.py"), encoding="utf-8") as fobj:
self.assertMultiLineEqual(module2.as_string(), fobj.read())
def test_as_string(self) -> None:
"""Check as_string for python syntax >= 2.7."""
code = """one_two = {1, 2}
b = {v: k for (k, v) in enumerate('string')}
cdd = {k for k in b}\n\n"""
ast = abuilder.string_build(code)
self.assertMultiLineEqual(ast.as_string(), code)
def test_3k_as_string(self) -> None:
"""Check as_string for python 3k syntax."""
code = """print()
def function(var):
nonlocal counter
try:
hello
except NameError as nexc:
(*hell, o) = b'hello'
raise AttributeError from nexc
\n"""
ast = abuilder.string_build(code)
self.assertEqual(ast.as_string(), code)
def test_docstring_as_string_roundtrip(self) -> None:
"""A docstring must not be able to break out of or inject into as_string()."""
docstrings = (
'ends with a quote"',
'has a """ triple quote',
"a\x00b", # NUL collides with the newline sentinel
"carriage\rreturn",
"back\\slash",
)
templates = ("def f():\n {}\n x = 1", "class C:\n {}\n x = 1")
for doc in docstrings:
for template in templates:
rebuilt = abuilder.string_build(template.format(repr(doc)))
# The rendered source must reparse to the same docstring, with no
# statement smuggled in past the literal.
scope = abuilder.string_build(rebuilt.as_string()).body[0]
self.assertEqual(scope.doc_node.value, doc)
self.assertEqual(len(scope.body), 1)
module = abuilder.string_build(repr('a module """ docstring'))
self.assertEqual(
abuilder.string_build(module.as_string()).doc_node.value,
'a module """ docstring',
)
def test_3k_annotations_and_metaclass(self) -> None:
code = '''
def function(var: int):
nonlocal counter
class Language(metaclass=Natural):
"""natural language"""
'''
code_annotations = textwrap.dedent(code)
expected = '''\
def function(var: int):
nonlocal counter
class Language(metaclass=Natural):
"""natural language"""'''
ast = abuilder.string_build(code_annotations)
self.assertEqual(ast.as_string().strip(), expected)
def test_ellipsis(self) -> None:
ast = abuilder.string_build("a[...]").body[0]
self.assertEqual(ast.as_string(), "a[...]")
def test_slices(self) -> None:
for code in (
"a[0]",
"a[1:3]",
"a[:-1:step]",
"a[:, newaxis]",
"a[newaxis, :]",
"del L[::2]",
"del A[1]",
"del Br[:]",
):
ast = abuilder.string_build(code).body[0]
self.assertEqual(ast.as_string(), code)
def test_slice_and_subscripts(self) -> None:
code = """a[:1] = bord[2:]
a[:1] = bord[2:]
del bree[3:d]
bord[2:]
del av[d::f], a[df:]
a[:1] = bord[2:]
del SRC[::1, newaxis, 1:]
tous[vals] = 1010
del thousand[key]
del a[::2], a[:-1:step]
del Fee.form[left:]
aout.vals = miles.of_stuff
del (ccok, (name.thing, foo.attrib.value)), Fee.form[left:]
if all[1] == bord[0:]:
pass\n\n"""
ast = abuilder.string_build(code)
self.assertEqual(ast.as_string(), code)
def test_int_attribute(self) -> None:
code = """
x = (-3).real
y = (3).imag
"""
ast = abuilder.string_build(code)
self.assertEqual(ast.as_string().strip(), code.strip())
def test_operator_precedence(self) -> None:
with open(resources.find("data/operator_precedence.py"), encoding="utf-8") as f:
for code in f:
self.check_as_string_ast_equality(code)
@staticmethod
def check_as_string_ast_equality(code: str) -> None:
"""
Check that as_string produces source code with exactly the same
semantics as the source it was originally parsed from.
"""
pre = builder.parse(code)
post = builder.parse(pre.as_string())
pre_repr = pre.repr_tree()
post_repr = post.repr_tree()
assert pre_repr == post_repr
assert pre.as_string().strip() == code.strip()
def test_class_def(self) -> None:
code = """
import abc
from typing import Tuple
class A:
pass
class B(metaclass=A, x=1):
pass
class C(B):
pass
class D(metaclass=abc.ABCMeta):
pass
def func(param: Tuple):
pass
"""
ast = abuilder.string_build(code)
self.assertEqual(ast.as_string().strip(), code.strip())
def test_f_strings(self):
code = r'''
a = f"{'a'}"
b = f'{{b}}'
c = f""" "{'c'}" """
d = f'{d!r} {d!s} {d!a}'
e = f'{e:.3}'
f = f'{f:{x}.{y}}'
n = f'\n'
everything = f""" " \' \r \t \\ {{ }} {'x' + x!r:a} {["'"]!s:{a}}"""
'''
ast = abuilder.string_build(code)
self.assertEqual(ast.as_string().strip(), code.strip())
@staticmethod
def test_as_string_unknown() -> None:
unknown1 = nodes.Unknown(parent=SYNTHETIC_ROOT)
unknown2 = nodes.Unknown(lineno=1, col_offset=0, parent=SYNTHETIC_ROOT)
assert unknown1.as_string() == "Unknown.Unknown()"
assert unknown2.as_string() == "Unknown.Unknown()"
@staticmethod
@pytest.mark.skipif(
IS_PYPY,
reason="Test requires manipulating the recursion limit, which cannot "
"be undone in a finally block without polluting other tests on PyPy.",
)
def test_recursion_error_trapped() -> None:
with pytest.warns(UserWarning, match="unable to transform"):
ast = abuilder.string_build(LONG_CHAINED_METHOD_CALL)
attribute = ast.body[1].value.func
with pytest.raises(UserWarning):
attribute.as_string()
@pytest.mark.skipif(not PY312_PLUS, reason="Uses 3.12 type param nodes")
class AsStringTypeParamNodes(unittest.TestCase):
@staticmethod
def test_as_string_type_alias() -> None:
ast1 = abuilder.string_build("type Point = tuple[float, float]")
type_alias1 = ast1.body[0]
assert type_alias1.as_string().strip() == "type Point = tuple[float, float]"
ast2 = abuilder.string_build(
"type Point[T, **P] = tuple[float, T, Callable[P, None]]"
)
type_alias2 = ast2.body[0]
assert (
type_alias2.as_string().strip()
== "type Point[T, **P] = tuple[float, T, Callable[P, None]]"
)
@staticmethod
def test_as_string_type_var() -> None:
ast = abuilder.string_build("type Point[T: int | str] = tuple[float, float]")
type_var = ast.body[0].type_params[0]
assert type_var.as_string().strip() == "T: int | str"
@staticmethod
@pytest.mark.skipif(
not PY313_PLUS, reason="Type parameter defaults were added in 313"
)
def test_as_string_type_var_default() -> None:
ast = abuilder.string_build(
"type Point[T: int | str = int] = tuple[float, float]"
)
type_var = ast.body[0].type_params[0]
assert type_var.as_string().strip() == "T: int | str = int"
@staticmethod
def test_as_string_type_var_tuple() -> None:
ast = abuilder.string_build("type Alias[*Ts] = tuple[*Ts]")
type_var_tuple = ast.body[0].type_params[0]
assert type_var_tuple.as_string().strip() == "*Ts"
@staticmethod
@pytest.mark.skipif(
not PY313_PLUS, reason="Type parameter defaults were added in 313"
)
def test_as_string_type_var_tuple_defaults() -> None:
ast = abuilder.string_build("type Alias[*Ts = tuple[int, str]] = tuple[*Ts]")
type_var_tuple = ast.body[0].type_params[0]
assert type_var_tuple.as_string().strip() == "*Ts = tuple[int, str]"
@staticmethod
def test_as_string_param_spec() -> None:
ast = abuilder.string_build("type Alias[**P] = Callable[P, int]")
param_spec = ast.body[0].type_params[0]
assert param_spec.as_string().strip() == "**P"
@staticmethod
@pytest.mark.skipif(
not PY313_PLUS, reason="Type parameter defaults were added in 313"
)
def test_as_string_param_spec_defaults() -> None:
ast = abuilder.string_build("type Alias[**P = [str, int]] = Callable[P, int]")
param_spec = ast.body[0].type_params[0]
assert param_spec.as_string().strip() == "**P = [str, int]"
@staticmethod
def test_as_string_class_type_params() -> None:
code = abuilder.string_build("class A[T, **P]: ...")
cls_node = code.body[0]
assert cls_node.as_string().strip() == "class A[T, **P]:\n ..."
@staticmethod
def test_as_string_function_type_params() -> None:
code = abuilder.string_build("def func[T, **P](): ...")
func_node = code.body[0]
assert func_node.as_string().strip() == "def func[T, **P]():\n ..."
class _NodeTest(unittest.TestCase):
"""Test transformation of If Node."""
CODE = ""
@property
def astroid(self) -> nodes.Module:
try:
return self.__class__.__dict__["CODE_Astroid"]
except KeyError:
module = builder.parse(self.CODE)
self.__class__.CODE_Astroid = module
return module
class IfNodeTest(_NodeTest):
"""Test transformation of If Node."""
CODE = """
if 0:
print()
if True:
print()
else:
pass
if "":
print()
elif []:
raise
if 1:
print()
elif True:
print()
elif func():
pass
else:
raise
if 1:
print()
elif (
2
and 3
):
print()
else:
# This is using else in a comment
raise
"""
def test_if_elif_else_node(self) -> None:
"""Test transformation for If node."""
self.assertEqual(len(self.astroid.body), 5)
for stmt in self.astroid.body:
self.assertIsInstance(stmt, nodes.If)
self.assertFalse(self.astroid.body[0].orelse) # simple If
self.assertIsInstance(self.astroid.body[1].orelse[0], nodes.Pass) # If / else
self.assertIsInstance(self.astroid.body[2].orelse[0], nodes.If) # If / elif
self.assertIsInstance(self.astroid.body[3].orelse[0].orelse[0], nodes.If)
def test_block_range(self) -> None:
"""Test block_range of various scope constructs"""
# Module
self.assertEqual(self.astroid.block_range(1), (0, 33))
# NOTE: Module does not consider the lineno argument. It would be more consistent to make
# this return (10, 33) but without a use case it seems better to not change behaviour.
self.assertEqual(self.astroid.block_range(10), (0, 33))
# if
self.assertEqual(self.astroid.body[0].block_range(2), (2, 3))
self.assertEqual(self.astroid.body[0].block_range(3), (3, 3))
# if ... else
self.assertEqual(self.astroid.body[1].block_range(5), (5, 6))
self.assertEqual(self.astroid.body[1].block_range(6), (6, 6))
self.assertEqual(self.astroid.body[1].block_range(7), (7, 8))
self.assertEqual(self.astroid.body[1].block_range(8), (8, 8))
# if ... elif
self.assertEqual(self.astroid.body[2].block_range(10), (10, 11))
self.assertEqual(self.astroid.body[2].block_range(11), (11, 11))
self.assertEqual(self.astroid.body[2].block_range(12), (12, 13))
self.assertEqual(self.astroid.body[2].block_range(13), (13, 13))
# if ... elif ... elif ... else
self.assertEqual(self.astroid.body[3].block_range(15), (15, 16))
self.assertEqual(self.astroid.body[3].block_range(16), (16, 16))
self.assertEqual(self.astroid.body[3].block_range(17), (17, 18))
self.assertEqual(self.astroid.body[3].block_range(18), (18, 18))
self.assertEqual(self.astroid.body[3].block_range(19), (19, 20))
self.assertEqual(self.astroid.body[3].block_range(20), (20, 20))
self.assertEqual(self.astroid.body[3].block_range(21), (21, 22))
self.assertEqual(self.astroid.body[3].block_range(22), (22, 22))
# if ... elif ... else
self.assertEqual(self.astroid.body[4].block_range(24), (24, 25))
self.assertEqual(self.astroid.body[4].block_range(25), (25, 25))
self.assertEqual(self.astroid.body[4].block_range(26), (26, 30))
self.assertEqual(self.astroid.body[4].block_range(27), (27, 30))
self.assertEqual(self.astroid.body[4].block_range(28), (28, 30))
self.assertEqual(self.astroid.body[4].block_range(29), (29, 30))
self.assertEqual(self.astroid.body[4].block_range(30), (30, 30))
self.assertEqual(self.astroid.body[4].block_range(31), (31, 33))
self.assertEqual(self.astroid.body[4].block_range(32), (32, 33))
self.assertEqual(self.astroid.body[4].block_range(33), (33, 33))
class TryNodeTest(_NodeTest):
CODE = """
try: # L2
print("Hello")
except IOError:
pass
except UnicodeError:
pass
else:
print()
finally:
print()
"""
def test_block_range(self) -> None:
try_node = self.astroid.body[0]
assert try_node.block_range(1) == (1, 11)
assert try_node.block_range(2) == (2, 3)
assert try_node.block_range(3) == (3, 3)
assert try_node.block_range(4) == (4, 5)
assert try_node.block_range(5) == (5, 5)
assert try_node.block_range(6) == (6, 7)
assert try_node.block_range(7) == (7, 7)
assert try_node.block_range(8) == (8, 9)
assert try_node.block_range(9) == (9, 9)
assert try_node.block_range(10) == (10, 11)
assert try_node.block_range(11) == (11, 11)
class ImportNodeTest(resources.SysPathSetup, unittest.TestCase):
def setUp(self) -> None:
super().setUp()
self.module = resources.build_file("data/module.py", "data.module")
self.module2 = resources.build_file("data/module2.py", "data.module2")
@pytest.mark.skipif(not PY315_PLUS, reason="PEP 810 lazy imports, new in 3.15")
def test_lazy_import(self) -> None:
node = extract_node("lazy import json")
assert isinstance(node, nodes.Import)
assert node.is_lazy == 1
self.assertEqual(node.as_string(), "lazy import json")
@pytest.mark.skipif(not PY315_PLUS, reason="PEP 810 lazy imports, new in 3.15")
def test_lazy_import_from(self) -> None:
node = extract_node("lazy from pathlib import Path")
assert isinstance(node, nodes.ImportFrom)
assert node.is_lazy == 1
self.assertEqual(node.as_string(), "lazy from pathlib import Path")
def test_eager_import_is_not_lazy(self) -> None:
# The ``is_lazy`` flag defaults to 0 on every supported version.
assert extract_node("import json").is_lazy == 0
assert extract_node("from pathlib import Path").is_lazy == 0
def test_import_self_resolve(self) -> None:
myos = next(self.module2.igetattr("myos"))
self.assertTrue(isinstance(myos, nodes.Module), myos)
self.assertEqual(myos.name, "os")
self.assertEqual(myos.qname(), "os")
self.assertEqual(myos.pytype(), "builtins.module")
def test_from_self_resolve(self) -> None:
namenode = next(self.module.igetattr("NameNode"))
self.assertTrue(isinstance(namenode, nodes.ClassDef), namenode)
self.assertEqual(namenode.root().name, "astroid.nodes.node_classes")
self.assertEqual(namenode.qname(), "astroid.nodes.node_classes.Name")
self.assertEqual(namenode.pytype(), "builtins.type")
abspath = next(self.module2.igetattr("abspath"))
self.assertTrue(isinstance(abspath, nodes.FunctionDef), abspath)
self.assertEqual(abspath.root().name, "os.path")
self.assertEqual(abspath.pytype(), "builtins.function")
if sys.platform != "win32":
# Not sure what is causing this check to fail on Windows.
# For some reason the abspath() inference returns a different
# path than expected:
# AssertionError: 'os.path._abspath_fallback' != 'os.path.abspath'
self.assertEqual(abspath.qname(), "os.path.abspath")
def test_real_name(self) -> None:
from_ = self.module["NameNode"]
self.assertEqual(from_.real_name("NameNode"), "Name")
imp_ = self.module["os"]
self.assertEqual(imp_.real_name("os"), "os")
self.assertRaises(AttributeInferenceError, imp_.real_name, "os.path")
imp_ = self.module["NameNode"]
self.assertEqual(imp_.real_name("NameNode"), "Name")
self.assertRaises(AttributeInferenceError, imp_.real_name, "Name")
imp_ = self.module2["YO"]
self.assertEqual(imp_.real_name("YO"), "YO")
self.assertRaises(AttributeInferenceError, imp_.real_name, "data")
def test_as_string(self) -> None:
ast = self.module["modutils"]
self.assertEqual(ast.as_string(), "from astroid import modutils")
ast = self.module["NameNode"]
self.assertEqual(ast.as_string(), "from astroid.nodes import Name as NameNode")
ast = self.module["os"]
self.assertEqual(ast.as_string(), "import os.path")
code = """from . import here
from .. import door
from .store import bread
from ..cave import wine\n\n"""
ast = abuilder.string_build(code)
self.assertMultiLineEqual(ast.as_string(), code)
def test_bad_import_inference(self) -> None:
# Explication of bug
"""When we import PickleError from nonexistent, a call to the infer
method of this From node will be made by unpack_infer.
inference.infer_from will try to import this module, which will fail and
raise a InferenceException (by ImportNode.do_import_module). The infer_name
will catch this exception and yield and Uninferable instead.
"""
code = """
try:
from pickle import PickleError
except ImportError:
from nonexistent import PickleError
try:
pass
except PickleError:
pass
"""
module = builder.parse(code)
handler_type = module.body[1].handlers[0].type
excs = list(nodes.unpack_infer(handler_type))
# The number of returned object can differ on Python 2
# and Python 3. In one version, an additional item will
# be returned, from the _pickle module, which is not
# present in the other version.
self.assertIsInstance(excs[0], nodes.ClassDef)
self.assertEqual(excs[0].name, "PickleError")
self.assertIs(excs[-1], util.Uninferable)
def test_absolute_import(self) -> None:
module = resources.build_file("data/absimport.py")
ctx = InferenceContext()
# will fail if absolute import failed
ctx.lookupname = "message"
next(module["message"].infer(ctx))
ctx.lookupname = "email"
m = next(module["email"].infer(ctx))
self.assertFalse(m.file.startswith(os.path.join("data", "email.py")))
def test_more_absolute_import(self) -> None:
module = resources.build_file("data/module1abs/__init__.py", "data.module1abs")
self.assertIn("sys", module.locals)
_pickle_names = ("dump",) # "dumps", "load", "loads")
def test_conditional(self) -> None:
module = resources.build_file("data/conditional_import/__init__.py")
ctx = InferenceContext()
for name in self._pickle_names:
ctx.lookupname = name
some = list(module[name].infer(ctx))
assert Uninferable not in some, name
def test_conditional_import(self) -> None:
module = resources.build_file("data/conditional.py")
ctx = InferenceContext()
for name in self._pickle_names:
ctx.lookupname = name
some = list(module[name].infer(ctx))
assert Uninferable not in some, name
class CmpNodeTest(unittest.TestCase):
def test_as_string(self) -> None:
ast = abuilder.string_build("a == 2").body[0]
self.assertEqual(ast.as_string(), "a == 2")
class ConstNodeTest(unittest.TestCase):
def _test(self, value: Any) -> None:
node = nodes.const_factory(value)
self.assertIsInstance(node._proxied, nodes.ClassDef)
self.assertEqual(node._proxied.name, value.__class__.__name__)
self.assertIs(node.value, value)
self.assertTrue(node._proxied.parent)
self.assertEqual(node._proxied.root().name, value.__class__.__module__)
with self.assertRaises(StatementMissing):
node.statement()
assert node.frame() is SYNTHETIC_ROOT
def test_none(self) -> None:
self._test(None)
def test_bool(self) -> None:
self._test(True)
def test_int(self) -> None:
self._test(1)
def test_float(self) -> None:
self._test(1.0)
def test_complex(self) -> None:
self._test(1.0j)
def test_str(self) -> None:
self._test("a")
def test_unicode(self) -> None:
self._test("a")
def test_str_kind(self):
node = builder.extract_node("""
const = u"foo"
""")
assert isinstance(node.value, nodes.Const)
assert node.value.value == "foo"
assert node.value.kind, "u"
def test_copy(self) -> None:
"""Make sure copying a Const object doesn't result in infinite recursion."""
const = copy.copy(nodes.Const(1))
assert const.value == 1
class NameNodeTest(unittest.TestCase):
def test_assign_to_true(self) -> None:
"""Test that True and False assignments don't crash."""
code = """
True = False
def hello(False):
pass
del True
"""
with self.assertRaises(AstroidBuildingError):
builder.parse(code)
class TestNamedExprNode:
"""Tests for the NamedExpr node."""
@staticmethod
def test_frame() -> None:
"""Test if the frame of NamedExpr is correctly set for certain types
of parent nodes.
"""
module = builder.parse("""
def func(var_1):
pass
def func_two(var_2, var_2 = (named_expr_1 := "walrus")):
pass
class MyBaseClass:
pass
class MyInheritedClass(MyBaseClass, var_3=(named_expr_2 := "walrus")):
pass
VAR = lambda y = (named_expr_3 := "walrus"): print(y)
def func_with_lambda(
var_5 = (
named_expr_4 := lambda y = (named_expr_5 := "walrus"): y
)
):
pass
COMPREHENSION = [y for i in (1, 2) if (y := i ** 2)]
""")
function = module.body[0]
assert function.args.frame() == function
assert function.args.frame() == function
function_two = module.body[1]
assert function_two.args.args[0].frame() == function_two
assert function_two.args.args[0].frame() == function_two
assert function_two.args.args[1].frame() == function_two
assert function_two.args.args[1].frame() == function_two
assert function_two.args.defaults[0].frame() == module
assert function_two.args.defaults[0].frame() == module
inherited_class = module.body[3]
assert inherited_class.keywords[0].frame() == inherited_class
assert inherited_class.keywords[0].frame() == inherited_class
assert inherited_class.keywords[0].value.frame() == module
assert inherited_class.keywords[0].value.frame() == module
lambda_assignment = module.body[4].value
assert lambda_assignment.args.args[0].frame() == lambda_assignment
assert lambda_assignment.args.args[0].frame() == lambda_assignment
assert lambda_assignment.args.defaults[0].frame() == module
assert lambda_assignment.args.defaults[0].frame() == module
lambda_named_expr = module.body[5].args.defaults[0]
assert lambda_named_expr.value.args.defaults[0].frame() == module
assert lambda_named_expr.value.args.defaults[0].frame() == module
comprehension = module.body[6].value
assert comprehension.generators[0].ifs[0].frame() == module
assert comprehension.generators[0].ifs[0].frame() == module
@staticmethod
def test_scope() -> None:
"""Test if the scope of NamedExpr is correctly set for certain types
of parent nodes.
"""
module = builder.parse("""
def func(var_1):
pass
def func_two(var_2, var_2 = (named_expr_1 := "walrus")):
pass
class MyBaseClass:
pass
class MyInheritedClass(MyBaseClass, var_3=(named_expr_2 := "walrus")):
pass
VAR = lambda y = (named_expr_3 := "walrus"): print(y)
def func_with_lambda(
var_5 = (
named_expr_4 := lambda y = (named_expr_5 := "walrus"): y
)
):
pass
COMPREHENSION = [y for i in (1, 2) if (y := i ** 2)]
""")
function = module.body[0]
assert function.args.scope() == function
function_two = module.body[1]
assert function_two.args.args[0].scope() == function_two
assert function_two.args.args[1].scope() == function_two
assert function_two.args.defaults[0].scope() == module
inherited_class = module.body[3]
assert inherited_class.keywords[0].scope() == inherited_class
assert inherited_class.keywords[0].value.scope() == module
lambda_assignment = module.body[4].value
assert lambda_assignment.args.args[0].scope() == lambda_assignment
assert lambda_assignment.args.defaults[0].scope()
lambda_named_expr = module.body[5].args.defaults[0]
assert lambda_named_expr.value.args.defaults[0].scope() == module
comprehension = module.body[6].value
assert comprehension.generators[0].ifs[0].scope() == module
class AnnAssignNodeTest(unittest.TestCase):
def test_primitive(self) -> None:
code = textwrap.dedent("""
test: int = 5
""")
assign = builder.extract_node(code)
self.assertIsInstance(assign, nodes.AnnAssign)
self.assertEqual(assign.target.name, "test")
self.assertEqual(assign.annotation.name, "int")
self.assertEqual(assign.value.value, 5)
self.assertEqual(assign.simple, 1)
def test_primitive_without_initial_value(self) -> None:
code = textwrap.dedent("""
test: str
""")
assign = builder.extract_node(code)
self.assertIsInstance(assign, nodes.AnnAssign)
self.assertEqual(assign.target.name, "test")
self.assertEqual(assign.annotation.name, "str")
self.assertEqual(assign.value, None)
def test_complex(self) -> None:
code = textwrap.dedent("""
test: Dict[List[str]] = {}
""")
assign = builder.extract_node(code)
self.assertIsInstance(assign, nodes.AnnAssign)
self.assertEqual(assign.target.name, "test")
self.assertIsInstance(assign.annotation, nodes.Subscript)
self.assertIsInstance(assign.value, nodes.Dict)
def test_as_string(self) -> None:
code = textwrap.dedent("""
print()
test: int = 5
test2: str
test3: List[Dict[str, str]] = []
""")
ast = abuilder.string_build(code)
self.assertEqual(ast.as_string().strip(), code.strip())
class ArgumentsNodeTC(unittest.TestCase):
def test_linenumbering(self) -> None:
ast = builder.parse("""
def func(a,
b): pass
x = lambda x: None
""")
self.assertEqual(ast["func"].args.fromlineno, 2)
self.assertFalse(ast["func"].args.is_statement)
xlambda = next(ast["x"].infer())
self.assertEqual(xlambda.args.fromlineno, 4)
self.assertEqual(xlambda.args.tolineno, 4)
self.assertFalse(xlambda.args.is_statement)
def test_kwoargs(self) -> None:
ast = builder.parse("""
def func(*, x):
pass
""")
args = ast["func"].args
assert isinstance(args, nodes.Arguments)
assert args.is_argument("x")
assert args.kw_defaults == [None]
ast = builder.parse("""
def func(*, x = "default"):
pass
""")
args = ast["func"].args
assert isinstance(args, nodes.Arguments)
assert args.is_argument("x")
assert len(args.kw_defaults) == 1
assert isinstance(args.kw_defaults[0], nodes.Const)
assert args.kw_defaults[0].value == "default"
def test_positional_only(self):
ast = builder.parse("""
def func(x, /, y):
pass
""")
args = ast["func"].args
self.assertTrue(args.is_argument("x"))
self.assertTrue(args.is_argument("y"))
index, node = args.find_argname("x")
self.assertEqual(index, 0)
self.assertIsNotNone(node)
class UnboundMethodNodeTest(unittest.TestCase):
def test_no_super_getattr(self) -> None:
# This is a test for issue
# https://bitbucket.org/logilab/astroid/issue/91, which tests
# that UnboundMethod doesn't call super when doing .getattr.
ast = builder.parse("""
class A(object):
def test(self):
pass
meth = A.test
""")
node = next(ast["meth"].infer())
with self.assertRaises(AttributeInferenceError):
node.getattr("__missssing__")
name = node.getattr("__name__")[0]
self.assertIsInstance(name, nodes.Const)
self.assertEqual(name.value, "test")
class BoundMethodNodeTest(unittest.TestCase):
def _is_property(self, ast: nodes.Module, prop: str) -> None:
inferred = next(ast[prop].infer())
self.assertIsInstance(inferred, nodes.Const, prop)
self.assertEqual(inferred.value, 42, prop)
def test_is_standard_property(self) -> None:
# Test to make sure the Python-provided property decorators
# are properly interpreted as properties
ast = builder.parse("""
import abc
import functools
class A(object):
@property
def builtin_property(self): return 42
@abc.abstractproperty
def abc_property(self): return 42
@property
@abc.abstractmethod
def abstractmethod_property(self): return 42
@functools.cached_property
def functools_property(self): return 42
cls = A()
builtin_p = cls.builtin_property
abc_p = cls.abc_property
abstractmethod_p = cls.abstractmethod_property
functools_p = cls.functools_property
""")
for prop in (
"builtin_p",
"abc_p",
"abstractmethod_p",
"functools_p",
):
self._is_property(ast, prop)
@pytest.mark.skipif(not PY311_PLUS, reason="Uses enum.property introduced in 3.11")
def test_is_standard_property_py311(self) -> None:
# Test to make sure the Python-provided property decorators
# are properly interpreted as properties
ast = builder.parse("""
import enum
class A(object):
@enum.property
def enum_property(self): return 42
cls = A()
enum_p = cls.enum_property
""")
self._is_property(ast, "enum_p")
def test_is_possible_property(self) -> None:
# Test to make sure that decorators with POSSIBLE_PROPERTIES names
# are properly interpreted as properties
ast = builder.parse("""
# Not real decorators, but we don't care
def cachedproperty(): pass
def cached_property(): pass
def reify(): pass
def lazy_property(): pass
def lazyproperty(): pass
def lazy(): pass
def lazyattribute(): pass
def lazy_attribute(): pass
def LazyProperty(): pass
def DynamicClassAttribute(): pass
class A(object):
@cachedproperty
def cachedproperty(self): return 42
@cached_property
def cached_property(self): return 42
@reify
def reified(self): return 42
@lazy_property
def lazy_prop(self): return 42
@lazyproperty
def lazyprop(self): return 42
@lazy
def decorated_with_lazy(self): return 42
@lazyattribute
def lazyattribute(self): return 42
@lazy_attribute
def lazy_attribute(self): return 42
@LazyProperty
def LazyProperty(self): return 42
@DynamicClassAttribute
def DynamicClassAttribute(self): return 42
cls = A()
cachedp = cls.cachedproperty
cached_p = cls.cached_property
reified = cls.reified
lazy_prop = cls.lazy_prop
lazyprop = cls.lazyprop
decorated_with_lazy = cls.decorated_with_lazy
lazya = cls.lazyattribute
lazy_a = cls.lazy_attribute
LazyP = cls.LazyProperty
DynamicClassA = cls.DynamicClassAttribute
""")
for prop in (
"cachedp",
"cached_p",
"reified",
"lazy_prop",
"lazyprop",
"decorated_with_lazy",
"lazya",
"lazy_a",
"LazyP",
"DynamicClassA",
):
self._is_property(ast, prop)
def test_is_standard_property_subclass(self) -> None:
# Test to make sure that subclasses of the Python-provided property decorators
# are properly interpreted as properties
ast = builder.parse("""
import abc
import functools
from typing import Generic, TypeVar
class user_property(property): pass
class user_abc_property(abc.abstractproperty): pass
class user_functools_property(functools.cached_property): pass
T = TypeVar('T')
class annotated_user_functools_property(functools.cached_property[T], Generic[T]): pass
class A(object):
@user_property
def user_property(self): return 42
@user_abc_property
def user_abc_property(self): return 42
@user_functools_property
def user_functools_property(self): return 42
@annotated_user_functools_property
def annotated_user_functools_property(self): return 42
cls = A()
user_p = cls.user_property
user_abc_p = cls.user_abc_property
user_functools_p = cls.user_functools_property
annotated_user_functools_p = cls.annotated_user_functools_property
""")
for prop in (
"user_p",
"user_abc_p",
"user_functools_p",
"annotated_user_functools_p",
):
self._is_property(ast, prop)
@pytest.mark.skipif(not PY311_PLUS, reason="Uses enum.property introduced in 3.11")
def test_is_standard_property_subclass_py311(self) -> None:
# Test to make sure that subclasses of the Python-provided property decorators
# are properly interpreted as properties
ast = builder.parse("""
import enum
class user_enum_property(enum.property): pass
class A(object):
@user_enum_property
def user_enum_property(self): return 42
cls = A()
user_enum_p = cls.user_enum_property
""")
self._is_property(ast, "user_enum_p")
@pytest.mark.skipif(not PY312_PLUS, reason="Uses 3.12 generic typing syntax")
def test_is_standard_property_subclass_py312(self) -> None:
ast = builder.parse("""
from functools import cached_property
class annotated_user_cached_property[T](cached_property[T]):
pass
class A(object):
@annotated_user_cached_property
def annotated_user_cached_property(self): return 42
cls = A()
annotated_user_cached_p = cls.annotated_user_cached_property
""")
self._is_property(ast, "annotated_user_cached_p")
def test_is_not_property(self) -> None:
ast = builder.parse("""
from collections.abc import Iterator
class cached_property: pass
# If a decorator is named cached_property, we will accept it as a property,
# even if it isn't functools.cached_property.
# However, do not extend the same leniency to superclasses of decorators.
class wrong_superclass_type1(cached_property): pass
class wrong_superclass_type2(cached_property[float]): pass
cachedproperty = { float: int }
class wrong_superclass_type3(cachedproperty[float]): pass
class wrong_superclass_type4(Iterator[float]): pass
class A(object):
def no_decorator(self): return 42
def property(self): return 42
@wrong_superclass_type1
def wrong_superclass_type1(self): return 42
@wrong_superclass_type2
def wrong_superclass_type2(self): return 42
@wrong_superclass_type3
def wrong_superclass_type3(self): return 42
@wrong_superclass_type4
def wrong_superclass_type4(self): return 42
cls = A()
no_decorator = cls.no_decorator
not_prop = cls.property
bad_superclass1 = cls.wrong_superclass_type1
bad_superclass2 = cls.wrong_superclass_type2
bad_superclass3 = cls.wrong_superclass_type3
bad_superclass4 = cls.wrong_superclass_type4
""")
for prop in (
"no_decorator",
"not_prop",
"bad_superclass1",
"bad_superclass2",
"bad_superclass3",
"bad_superclass4",
):
inferred = next(ast[prop].infer())
self.assertIsInstance(inferred, bases.BoundMethod)
class AliasesTest(unittest.TestCase):
def setUp(self) -> None:
self.transformer = transforms.TransformVisitor()
def parse_transform(self, code: str) -> nodes.Module:
module = parse(code, apply_transforms=False)
return self.transformer.visit(module)
def test_aliases(self) -> None:
def test_from(node: nodes.ImportFrom) -> nodes.ImportFrom:
node.names = [*node.names, ("absolute_import", None)]
return node
def test_class(node: nodes.ClassDef) -> nodes.ClassDef:
node.name = "Bar"
return node
def test_function(node: nodes.FunctionDef) -> nodes.FunctionDef:
node.name = "another_test"
return node
def test_callfunc(node: nodes.Call) -> nodes.Call | None:
if node.func.name == "Foo":
node.func.name = "Bar"
return node
return None
def test_assname(node: nodes.AssignName) -> nodes.AssignName | None:
if node.name == "foo":
return nodes.AssignName(
"bar",
node.lineno,
node.col_offset,
node.parent,
end_lineno=node.end_lineno,
end_col_offset=node.end_col_offset,
)
return None
def test_assattr(node: nodes.AssignAttr) -> nodes.AssignAttr:
if node.attrname == "a":
node.attrname = "b"
return node
return None
def test_getattr(node: nodes.Attribute) -> nodes.Attribute:
if node.attrname == "a":
node.attrname = "b"
return node
return None
def test_genexpr(node: nodes.GeneratorExp) -> nodes.GeneratorExp:
if node.elt.value == 1:
node.elt = nodes.Const(2, node.lineno, node.col_offset, node.parent)
return node
return None
self.transformer.register_transform(nodes.ImportFrom, test_from)
self.transformer.register_transform(nodes.ClassDef, test_class)
self.transformer.register_transform(nodes.FunctionDef, test_function)
self.transformer.register_transform(nodes.Call, test_callfunc)
self.transformer.register_transform(nodes.AssignName, test_assname)
self.transformer.register_transform(nodes.AssignAttr, test_assattr)
self.transformer.register_transform(nodes.Attribute, test_getattr)
self.transformer.register_transform(nodes.GeneratorExp, test_genexpr)
string = """
from __future__ import print_function
class Foo: pass
def test(a): return a
foo = Foo()
foo.a = test(42)
foo.a
(1 for _ in range(0, 42))
"""
module = self.parse_transform(string)
self.assertEqual(len(module.body[0].names), 2)
self.assertIsInstance(module.body[0], nodes.ImportFrom)
self.assertEqual(module.body[1].name, "Bar")
self.assertIsInstance(module.body[1], nodes.ClassDef)
self.assertEqual(module.body[2].name, "another_test")
self.assertIsInstance(module.body[2], nodes.FunctionDef)
self.assertEqual(module.body[3].targets[0].name, "bar")
self.assertIsInstance(module.body[3].targets[0], nodes.AssignName)
self.assertEqual(module.body[3].value.func.name, "Bar")
self.assertIsInstance(module.body[3].value, nodes.Call)
self.assertEqual(module.body[4].targets[0].attrname, "b")
self.assertIsInstance(module.body[4].targets[0], nodes.AssignAttr)
self.assertIsInstance(module.body[5], nodes.Expr)
self.assertEqual(module.body[5].value.attrname, "b")
self.assertIsInstance(module.body[5].value, nodes.Attribute)
self.assertEqual(module.body[6].value.elt.value, 2)
self.assertIsInstance(module.body[6].value, nodes.GeneratorExp)
class Python35AsyncTest(unittest.TestCase):
def test_async_await_keywords(self) -> None:
(
async_def,
async_for,
async_with,
async_for2,
async_with2,
await_node,
) = builder.extract_node(
"""
async def func(): #@
async for i in range(10): #@
f = __(await i)
async with test(): #@
pass
async for i \
in range(10): #@
pass
async with test(), \
test2(): #@
pass
"""
)
assert isinstance(async_def, nodes.AsyncFunctionDef)
assert async_def.lineno == 2
assert async_def.col_offset == 0
assert isinstance(async_for, nodes.AsyncFor)
assert async_for.lineno == 3
assert async_for.col_offset == 4
assert isinstance(async_with, nodes.AsyncWith)
assert async_with.lineno == 5
assert async_with.col_offset == 4
assert isinstance(async_for2, nodes.AsyncFor)
assert async_for2.lineno == 7
assert async_for2.col_offset == 4
assert isinstance(async_with2, nodes.AsyncWith)
assert async_with2.lineno == 9
assert async_with2.col_offset == 4
assert isinstance(await_node, nodes.Await)
assert isinstance(await_node.value, nodes.Name)
assert await_node.lineno == 4
assert await_node.col_offset == 15
def _test_await_async_as_string(self, code: str) -> None:
ast_node = parse(code)
self.assertEqual(ast_node.as_string().strip(), code.strip())
def test_await_as_string(self) -> None:
code = textwrap.dedent("""
async def function():
await 42
await x[0]
(await x)[0]
await (x + y)[0]
""")
self._test_await_async_as_string(code)
def test_asyncwith_as_string(self) -> None:
code = textwrap.dedent("""
async def function():
async with 42:
pass
""")
self._test_await_async_as_string(code)
def test_asyncfor_as_string(self) -> None:
code = textwrap.dedent("""
async def function():
async for i in range(10):
await 42
""")
self._test_await_async_as_string(code)
def test_decorated_async_def_as_string(self) -> None:
code = textwrap.dedent("""
@decorator
async def function():
async for i in range(10):
await 42
""")
self._test_await_async_as_string(code)
class ContextTest(unittest.TestCase):
def test_subscript_load(self) -> None:
node = builder.extract_node("f[1]")
self.assertIs(node.ctx, Context.Load)
def test_subscript_del(self) -> None:
node = builder.extract_node("del f[1]")
self.assertIs(node.targets[0].ctx, Context.Del)
def test_subscript_store(self) -> None:
node = builder.extract_node("f[1] = 2")
subscript = node.targets[0]
self.assertIs(subscript.ctx, Context.Store)
def test_list_load(self) -> None:
node = builder.extract_node("[]")
self.assertIs(node.ctx, Context.Load)
def test_list_del(self) -> None:
node = builder.extract_node("del []")
self.assertIs(node.targets[0].ctx, Context.Del)
def test_list_store(self) -> None:
with self.assertRaises(AstroidSyntaxError):
builder.extract_node("[0] = 2")
def test_tuple_load(self) -> None:
node = builder.extract_node("(1, )")
self.assertIs(node.ctx, Context.Load)
def test_tuple_store(self) -> None:
with self.assertRaises(AstroidSyntaxError):
builder.extract_node("(1, ) = 3")
def test_starred_load(self) -> None:
node = builder.extract_node("a = *b")
starred = node.value
self.assertIs(starred.ctx, Context.Load)
def test_starred_store(self) -> None:
node = builder.extract_node("a, *b = 1, 2")
starred = node.targets[0].elts[1]
self.assertIs(starred.ctx, Context.Store)
def test_unknown() -> None:
"""Test Unknown node."""
assert isinstance(next(UNATTACHED_UNKNOWN.infer()), type(util.Uninferable))
assert isinstance(UNATTACHED_UNKNOWN.name, str)
assert isinstance(UNATTACHED_UNKNOWN.qname(), str)
def test_type_comments_with() -> None:
module = builder.parse("""
with a as b: # type: int
pass
with a as b: # type: ignore[name-defined]
pass
""")
node = module.body[0]
ignored_node = module.body[1]
assert isinstance(node.type_annotation, nodes.Name)
assert ignored_node.type_annotation is None
def test_type_comments_for() -> None:
module = builder.parse("""
for a, b in [1, 2, 3]: # type: List[int]
pass
for a, b in [1, 2, 3]: # type: ignore[name-defined]
pass
""")
node = module.body[0]
ignored_node = module.body[1]
assert isinstance(node.type_annotation, nodes.Subscript)
assert node.type_annotation.as_string() == "List[int]"
assert ignored_node.type_annotation is None
def test_type_coments_assign() -> None:
module = builder.parse("""
a, b = [1, 2, 3] # type: List[int]
a, b = [1, 2, 3] # type: ignore[name-defined]
""")
node = module.body[0]
ignored_node = module.body[1]
assert isinstance(node.type_annotation, nodes.Subscript)
assert node.type_annotation.as_string() == "List[int]"
assert ignored_node.type_annotation is None
def test_type_comments_invalid_expression() -> None:
module = builder.parse("""
a, b = [1, 2, 3] # type: something completely invalid
a, b = [1, 2, 3] # typeee: 2*+4
a, b = [1, 2, 3] # type: List[int
""")
for node in module.body:
assert node.type_annotation is None
def test_type_comments_invalid_function_comments() -> None:
module = builder.parse("""
def func(
# type: () -> int # inside parentheses
):
pass
def func():
# type: something completely invalid
pass
def func1():
# typeee: 2*+4
pass
def func2():
# type: List[int
pass
""")
for node in module.body:
assert node.type_comment_returns is None
assert node.type_comment_args is None
def test_type_comments_pathological_assign() -> None:
"""Regression test for issue #2993.
A type comment with deeply nested braces causes ``ast.parse`` to raise
``MemoryError`` (or ``ValueError`` on some interpreters). astroid should
treat the type comment as invalid instead of crashing.
"""
code = "a = b # type:i" + "{" * 200
module = builder.parse(code)
assert module.body[0].type_annotation is None
def test_type_comments_pathological_function() -> None:
"""Regression test for issue #2993 covering function type comments."""
code = "def func():\n # type: i" + "{" * 200 + "\n pass\n"
module = builder.parse(code)
assert module.body[0].type_comment_returns is None
assert module.body[0].type_comment_args is None
def test_type_comments_function() -> None:
module = builder.parse("""
def func():
# type: (int) -> str
pass
def func1():
# type: (int, int, int) -> (str, str)
pass
def func2():
# type: (int, int, str, List[int]) -> List[int]
pass
""")
expected_annotations = [
(["int"], nodes.Name, "str"),
(["int", "int", "int"], nodes.Tuple, "(str, str)"),
(["int", "int", "str", "List[int]"], nodes.Subscript, "List[int]"),
]
for node, (expected_args, expected_returns_type, expected_returns_string) in zip(
module.body, expected_annotations
):
assert node.type_comment_returns is not None
assert node.type_comment_args is not None
for expected_arg, actual_arg in zip(expected_args, node.type_comment_args):
assert actual_arg.as_string() == expected_arg
assert isinstance(node.type_comment_returns, expected_returns_type)
assert node.type_comment_returns.as_string() == expected_returns_string
def test_type_comments_arguments() -> None:
module = builder.parse("""
def func(
a, # type: int
):
# type: (...) -> str
pass
def func1(
a, # type: int
b, # type: int
c, # type: int
):
# type: (...) -> (str, str)
pass
def func2(
a, # type: int
b, # type: int
c, # type: str
d, # type: List[int]
):
# type: (...) -> List[int]
pass
""")
expected_annotations = [
["int"],
["int", "int", "int"],
["int", "int", "str", "List[int]"],
]
for node, expected_args in zip(module.body, expected_annotations):
assert len(node.type_comment_args) == 1
assert isinstance(node.type_comment_args[0], nodes.Const)
assert node.type_comment_args[0].value == Ellipsis
assert len(node.args.type_comment_args) == len(expected_args)
for expected_arg, actual_arg in zip(expected_args, node.args.type_comment_args):
assert actual_arg.as_string() == expected_arg
def test_type_comments_posonly_arguments() -> None:
module = builder.parse("""
def f_arg_comment(
a, # type: int
b, # type: int
/,
c, # type: Optional[int]
d, # type: Optional[int]
*,
e, # type: float
f, # type: float
):
# type: (...) -> None
pass
""")
expected_annotations = [
[["int", "int"], ["Optional[int]", "Optional[int]"], ["float", "float"]]
]
for node, expected_types in zip(module.body, expected_annotations):
assert len(node.type_comment_args) == 1
assert isinstance(node.type_comment_args[0], nodes.Const)
assert node.type_comment_args[0].value == Ellipsis
type_comments = [
node.args.type_comment_posonlyargs,
node.args.type_comment_args,
node.args.type_comment_kwonlyargs,
]
for expected_args, actual_args in zip(expected_types, type_comments):
assert len(expected_args) == len(actual_args)
for expected_arg, actual_arg in zip(expected_args, actual_args):
assert actual_arg.as_string() == expected_arg
def test_correct_function_type_comment_parent() -> None:
data = """
def f(a):
# type: (A) -> A
pass
"""
parsed_data = builder.parse(data)
f = parsed_data.body[0]
assert f.type_comment_args[0].parent is f
assert f.type_comment_returns.parent is f
def test_is_generator_for_yield_assignments() -> None:
node = astroid.extract_node("""
class A:
def test(self):
a = yield
while True:
print(a)
yield a
a = A()
a.test
""")
inferred = next(node.infer())
assert isinstance(inferred, astroid.BoundMethod)
assert bool(inferred.is_generator())
class AsyncGeneratorTest(unittest.TestCase):
def test_async_generator(self):
node = astroid.extract_node("""
async def a_iter(n):
for i in range(1, n + 1):
yield i
await asyncio.sleep(1)
a_iter(2) #@
""")
inferred = next(node.infer())
assert isinstance(inferred, bases.AsyncGenerator)
assert inferred.getattr("__aiter__")
assert inferred.getattr("__anext__")
assert inferred.pytype() == "builtins.async_generator"
assert inferred.display_type() == "AsyncGenerator"
def test_f_string_correct_line_numbering() -> None:
"""Test that we generate correct line numbers for f-strings."""
node = astroid.extract_node("""
def func_foo(arg_bar, arg_foo):
dict_foo = {}
f'{arg_bar.attr_bar}' #@
""")
assert node.lineno == 5
assert node.last_child().lineno == 5
assert node.last_child().last_child().lineno == 5
def test_assignment_expression() -> None:
code = """
if __(a := 1):
pass
if __(b := test):
pass
"""
first, second = astroid.extract_node(code)
assert isinstance(first.target, nodes.AssignName)
assert first.target.name == "a"
assert isinstance(first.value, nodes.Const)
assert first.value.value == 1
assert first.as_string() == "(a := 1)"
assert isinstance(second.target, nodes.AssignName)
assert second.target.name == "b"
assert isinstance(second.value, nodes.Name)
assert second.value.name == "test"
assert second.as_string() == "(b := test)"
def test_assignment_expression_as_string() -> None:
"""Regression test for https://github.com/pylint-dev/astroid/issues/2668."""
code = "if attn_output.size() != (size := (1, 2, 3, 4)):\n pass"
rendered = astroid.extract_node(code).as_string()
assert "(size := (1, 2, 3, 4))" in rendered
compile(rendered, "<astroid-as-string>", "exec")
def test_assignment_expression_compare_operands_as_string() -> None:
"""NamedExpr operands always render with their own parens."""
compare = astroid.parse("if (a := 10) != (a := 10):\n pass").body[0].test
assert isinstance(compare, nodes.Compare)
assert compare.left.as_string() == "a := 10"
assert compare.ops[0][1].as_string() == "a := 10"
assert compare.as_string() == "(a := 10) != (a := 10)"
def test_assignment_expression_in_functiondef() -> None:
code = """
def function(param = (assignment := "walrus")):
def inner_function(inner_param = (inner_assign := "walrus")):
pass
pass
class MyClass(attr = (assignment_two := "walrus")):
pass
VAR = lambda y = (assignment_three := "walrus"): print(y)
def func_with_lambda(
param=(named_expr_four := lambda y=(assignment_four := "walrus"): y),
):
pass
COMPREHENSION = [y for i in (1, 2) if (assignment_five := i ** 2)]
def func():
var = lambda y = (assignment_six := 2): print(y)
VAR_TWO = [
func(assignment_seven := 2)
for _ in (1,)
]
LAMBDA = lambda x: print(assignment_eight := x ** 2)
class SomeClass:
(assignment_nine := 2**2)
"""
module = astroid.parse(code)
assert "assignment" in module.locals
assert isinstance(module.locals.get("assignment")[0], nodes.AssignName)
function = module.body[0]
assert "inner_assign" in function.locals
assert "inner_assign" not in module.locals
assert isinstance(function.locals.get("inner_assign")[0], nodes.AssignName)
assert "assignment_two" in module.locals
assert isinstance(module.locals.get("assignment_two")[0], nodes.AssignName)
assert "assignment_three" in module.locals
assert isinstance(module.locals.get("assignment_three")[0], nodes.AssignName)
assert "assignment_four" in module.locals
assert isinstance(module.locals.get("assignment_four")[0], nodes.AssignName)
assert "assignment_five" in module.locals
assert isinstance(module.locals.get("assignment_five")[0], nodes.AssignName)
func = module.body[5]
assert "assignment_six" in func.locals
assert "assignment_six" not in module.locals
assert isinstance(func.locals.get("assignment_six")[0], nodes.AssignName)
assert "assignment_seven" in module.locals
assert isinstance(module.locals.get("assignment_seven")[0], nodes.AssignName)
lambda_assign = module.body[7]
assert "assignment_eight" in lambda_assign.value.locals
assert "assignment_eight" not in module.locals
assert isinstance(
lambda_assign.value.locals.get("assignment_eight")[0], nodes.AssignName
)
class_assign = module.body[8]
assert "assignment_nine" in class_assign.locals
assert "assignment_nine" not in module.locals
assert isinstance(class_assign.locals.get("assignment_nine")[0], nodes.AssignName)
def test_get_doc() -> None:
code = textwrap.dedent("""\
def func():
"Docstring"
return 1
""")
node: nodes.FunctionDef = astroid.extract_node(code) # type: ignore[assignment]
assert isinstance(node.doc_node, nodes.Const)
assert node.doc_node.value == "Docstring"
assert node.doc_node.lineno == 2
assert node.doc_node.col_offset == 4
assert node.doc_node.end_lineno == 2
assert node.doc_node.end_col_offset == 15
code = textwrap.dedent("""\
def func():
...
return 1
""")
node = astroid.extract_node(code)
assert node.doc_node is None
def test_parse_fstring_debug_mode() -> None:
node = astroid.extract_node('f"{3=}"')
assert isinstance(node, nodes.JoinedStr)
assert node.as_string() == "f'3={3!r}'"
def test_parse_type_comments_with_proper_parent() -> None:
code = """
class D: #@
@staticmethod
def g(
x # type: np.array
):
pass
"""
node = astroid.extract_node(code)
func = node.getattr("g")[0]
type_comments = func.args.type_comment_args
assert len(type_comments) == 1
type_comment = type_comments[0]
assert isinstance(type_comment, nodes.Attribute)
assert isinstance(type_comment.parent, nodes.Expr)
assert isinstance(type_comment.parent.parent, nodes.Arguments)
def test_const_itered() -> None:
code = 'a = "string"'
node = astroid.extract_node(code).value
assert isinstance(node, nodes.Const)
itered = node.itered()
assert len(itered) == 6
assert [elem.value for elem in itered] == list("string")
def test_is_generator_for_yield_in_while() -> None:
code = """
def paused_iter(iterable):
while True:
# Continue to yield the same item until `next(i)` or `i.send(False)`
while (yield value):
pass
"""
node = astroid.extract_node(code)
assert bool(node.is_generator())
def test_is_generator_for_yield_in_if() -> None:
code = """
import asyncio
def paused_iter(iterable):
if (yield from asyncio.sleep(0.01)):
pass
return
"""
node = astroid.extract_node(code)
assert bool(node.is_generator())
def test_is_generator_for_yield_in_aug_assign() -> None:
code = """
def test():
buf = ''
while True:
buf += yield
"""
node = astroid.extract_node(code)
assert bool(node.is_generator())
class TestPatternMatching:
@staticmethod
def test_match_simple():
code = textwrap.dedent("""
match status:
case 200:
pass
case 401 | 402 | 403:
pass
case None:
pass
case _:
pass
""").strip()
node = builder.extract_node(code)
assert node.as_string() == code
assert isinstance(node, nodes.Match)
assert isinstance(node.subject, nodes.Name)
assert node.subject.name == "status"
assert isinstance(node.cases, list) and len(node.cases) == 4
case0, case1, case2, case3 = node.cases
assert list(node.get_children()) == [node.subject, *node.cases]
assert isinstance(case0.pattern, nodes.MatchValue)
assert (
isinstance(case0.pattern.value, nodes.Const)
and case0.pattern.value.value == 200
)
assert list(case0.pattern.get_children()) == [case0.pattern.value]
assert case0.guard is None
assert isinstance(case0.body[0], nodes.Pass)
assert list(case0.get_children()) == [case0.pattern, case0.body[0]]
assert isinstance(case1.pattern, nodes.MatchOr)
assert (
isinstance(case1.pattern.patterns, list)
and len(case1.pattern.patterns) == 3
)
for i in range(3):
match_value = case1.pattern.patterns[i]
assert isinstance(match_value, nodes.MatchValue)
assert isinstance(match_value.value, nodes.Const)
assert match_value.value.value == (401, 402, 403)[i]
assert list(case1.pattern.get_children()) == case1.pattern.patterns
assert isinstance(case2.pattern, nodes.MatchSingleton)
assert case2.pattern.value is None
assert not list(case2.pattern.get_children())
assert isinstance(case3.pattern, nodes.MatchAs)
assert case3.pattern.name is None
assert case3.pattern.pattern is None
assert not list(case3.pattern.get_children())
@staticmethod
def test_match_sequence():
code = textwrap.dedent("""
match status:
case [x, 2, _, *rest] as y if x > 2:
pass
""").strip()
node = builder.extract_node(code)
assert node.as_string() == code
assert isinstance(node, nodes.Match)
assert isinstance(node.cases, list) and len(node.cases) == 1
case = node.cases[0]
assert isinstance(case.pattern, nodes.MatchAs)
assert isinstance(case.pattern.name, nodes.AssignName)
assert case.pattern.name.name == "y"
assert list(case.pattern.get_children()) == [
case.pattern.pattern,
case.pattern.name,
]
assert isinstance(case.guard, nodes.Compare)
assert isinstance(case.body[0], nodes.Pass)
assert list(case.get_children()) == [case.pattern, case.guard, case.body[0]]
pattern_seq = case.pattern.pattern
assert isinstance(pattern_seq, nodes.MatchSequence)
assert isinstance(pattern_seq.patterns, list) and len(pattern_seq.patterns) == 4
assert (
isinstance(pattern_seq.patterns[0], nodes.MatchAs)
and isinstance(pattern_seq.patterns[0].name, nodes.AssignName)
and pattern_seq.patterns[0].name.name == "x"
and pattern_seq.patterns[0].pattern is None
)
assert (
isinstance(pattern_seq.patterns[1], nodes.MatchValue)
and isinstance(pattern_seq.patterns[1].value, nodes.Const)
and pattern_seq.patterns[1].value.value == 2
)
assert (
isinstance(pattern_seq.patterns[2], nodes.MatchAs)
and pattern_seq.patterns[2].name is None
)
assert (
isinstance(pattern_seq.patterns[3], nodes.MatchStar)
and isinstance(pattern_seq.patterns[3].name, nodes.AssignName)
and pattern_seq.patterns[3].name.name == "rest"
)
assert list(pattern_seq.patterns[3].get_children()) == [
pattern_seq.patterns[3].name
]
assert list(pattern_seq.get_children()) == pattern_seq.patterns
@staticmethod
def test_match_mapping():
code = textwrap.dedent("""
match status:
case {0: x, 1: _}:
pass
case {**rest}:
pass
""").strip()
node = builder.extract_node(code)
assert node.as_string() == code
assert isinstance(node, nodes.Match)
assert isinstance(node.cases, list) and len(node.cases) == 2
case0, case1 = node.cases
assert isinstance(case0.pattern, nodes.MatchMapping)
assert case0.pattern.rest is None
assert isinstance(case0.pattern.keys, list) and len(case0.pattern.keys) == 2
assert (
isinstance(case0.pattern.patterns, list)
and len(case0.pattern.patterns) == 2
)
for i in range(2):
key = case0.pattern.keys[i]
assert isinstance(key, nodes.Const)
assert key.value == i
pattern = case0.pattern.patterns[i]
assert isinstance(pattern, nodes.MatchAs)
if i == 0:
assert isinstance(pattern.name, nodes.AssignName)
assert pattern.name.name == "x"
elif i == 1:
assert pattern.name is None
assert list(case0.pattern.get_children()) == [
*case0.pattern.keys,
*case0.pattern.patterns,
]
assert isinstance(case1.pattern, nodes.MatchMapping)
assert isinstance(case1.pattern.rest, nodes.AssignName)
assert case1.pattern.rest.name == "rest"
assert isinstance(case1.pattern.keys, list) and len(case1.pattern.keys) == 0
assert (
isinstance(case1.pattern.patterns, list)
and len(case1.pattern.patterns) == 0
)
assert list(case1.pattern.get_children()) == [case1.pattern.rest]
@staticmethod
def test_match_class():
code = textwrap.dedent("""
match x:
case Point2D(0, a):
pass
case Point3D(x=0, y=1, z=b):
pass
""").strip()
node = builder.extract_node(code)
assert node.as_string() == code
assert isinstance(node, nodes.Match)
assert isinstance(node.cases, list) and len(node.cases) == 2
case0, case1 = node.cases
assert isinstance(case0.pattern, nodes.MatchClass)
assert isinstance(case0.pattern.cls, nodes.Name)
assert case0.pattern.cls.name == "Point2D"
assert (
isinstance(case0.pattern.patterns, list)
and len(case0.pattern.patterns) == 2
)
match_value = case0.pattern.patterns[0]
assert (
isinstance(match_value, nodes.MatchValue)
and isinstance(match_value.value, nodes.Const)
and match_value.value.value == 0
)
match_as = case0.pattern.patterns[1]
assert (
isinstance(match_as, nodes.MatchAs)
and match_as.pattern is None
and isinstance(match_as.name, nodes.AssignName)
and match_as.name.name == "a"
)
assert list(case0.pattern.get_children()) == [
case0.pattern.cls,
*case0.pattern.patterns,
]
assert isinstance(case1.pattern, nodes.MatchClass)
assert isinstance(case1.pattern.cls, nodes.Name)
assert case1.pattern.cls.name == "Point3D"
assert (
isinstance(case1.pattern.patterns, list)
and len(case1.pattern.patterns) == 0
)
assert (
isinstance(case1.pattern.kwd_attrs, list)
and len(case1.pattern.kwd_attrs) == 3
)
assert (
isinstance(case1.pattern.kwd_patterns, list)
and len(case1.pattern.kwd_patterns) == 3
)
for i in range(2):
assert case1.pattern.kwd_attrs[i] == ("x", "y")[i]
kwd_pattern = case1.pattern.kwd_patterns[i]
assert isinstance(kwd_pattern, nodes.MatchValue)
assert isinstance(kwd_pattern.value, nodes.Const)
assert kwd_pattern.value.value == i
assert case1.pattern.kwd_attrs[2] == "z"
kwd_pattern = case1.pattern.kwd_patterns[2]
assert (
isinstance(kwd_pattern, nodes.MatchAs)
and kwd_pattern.pattern is None
and isinstance(kwd_pattern.name, nodes.AssignName)
and kwd_pattern.name.name == "b"
)
assert list(case1.pattern.get_children()) == [
case1.pattern.cls,
*case1.pattern.kwd_patterns,
]
@staticmethod
def test_return_from_match():
code = textwrap.dedent("""
def return_from_match(x):
match x:
case 10:
return 10
case _:
return -1
return_from_match(10) #@
""").strip()
node = builder.extract_node(code)
inferred = node.inferred()
assert len(inferred) == 2
assert [inf.value for inf in inferred] == [10, -1]
@pytest.mark.skipif(not PY314_PLUS, reason="TemplateStr was added in PY314")
class TestTemplateString:
@staticmethod
def test_template_string_simple() -> None:
code = textwrap.dedent("""
name = "Foo"
place = 3
t"{name} finished {place!r:ordinal}" #@
""").strip()
node = builder.extract_node(code)
assert node.as_string() == "t'{name} finished {place!r:ordinal}'"
assert isinstance(node, nodes.TemplateStr)
assert len(node.values) == 3
value = node.values[0]
assert isinstance(value, nodes.Interpolation)
assert isinstance(value.value, nodes.Name)
assert value.value.name == "name"
assert value.str == "name"
assert value.conversion == -1
assert value.format_spec is None
value = node.values[1]
assert isinstance(value, nodes.Const)
assert value.pytype() == "builtins.str"
assert value.value == " finished "
value = node.values[2]
assert isinstance(value, nodes.Interpolation)
assert isinstance(value.value, nodes.Name)
assert value.value.name == "place"
assert value.str == "place"
assert value.conversion == ord("r")
assert isinstance(value.format_spec, nodes.JoinedStr)
@staticmethod
def test_template_string_as_string() -> None:
code = textwrap.dedent("""
a = t'plain'
b = t''
c = t'{x}'
d = t'{x!r} {x!s} {x!a}'
e = t'{x:.3}'
f = t'{x:{width}.{precision}}'
g = t'{x!r:>{width}}'
h = t'prefix {x} infix {y} suffix'
i = t'{{ escaped }} {x}'
j = t'\\n\\t'
""").strip()
ast = abuilder.string_build(code)
assert ast.as_string().strip() == code
@staticmethod
def test_template_string_inference() -> None:
# A t-string evaluates to a ``string.templatelib.Template`` instance,
# not to a ``str`` (unlike an f-string).
node = builder.extract_node('t"{1} and {2}"')
inferred = list(node.infer())
assert len(inferred) == 1
instance = inferred[0]
assert isinstance(instance, bases.Instance)
assert instance.pytype() == "string.templatelib.Template"
# An interpolation field is a ``string.templatelib.Interpolation``.
interpolation = node.values[0]
assert isinstance(interpolation, nodes.Interpolation)
inferred_interp = list(interpolation.infer())
assert len(inferred_interp) == 1
assert isinstance(inferred_interp[0], bases.Instance)
assert inferred_interp[0].pytype() == "string.templatelib.Interpolation"
@staticmethod
def _member(instance, name):
"""Return the single inferred value of ``instance.<name>``."""
attr = instance.getattr(name)
assert len(attr) == 1
return attr[0]
@staticmethod
def _const_tuple(tuple_node):
assert isinstance(tuple_node, nodes.Tuple)
return [elt.value for elt in tuple_node.elts]
def test_template_string_member_inference(self) -> None:
# The members are reconstructed from the actual template contents,
# matching the PEP 750 runtime semantics.
node = builder.extract_node(textwrap.dedent("""
name = "World"
t"a {name!r:>{5}} b {1} c" #@
"""))
instance = next(node.infer())
assert isinstance(instance, bases.Instance)
assert instance.pytype() == "string.templatelib.Template"
# strings: constant segments, len == len(interpolations) + 1
assert self._const_tuple(self._member(instance, "strings")) == [
"a ",
" b ",
" c",
]
# values: the inferred interpolation values
assert self._const_tuple(self._member(instance, "values")) == ["World", 1]
interpolations = self._member(instance, "interpolations")
assert isinstance(interpolations, nodes.Tuple)
assert len(interpolations.elts) == 2
first = interpolations.elts[0]
assert first.pytype() == "string.templatelib.Interpolation"
assert self._member(first, "value").value == "World"
assert self._member(first, "expression").value == "name"
assert self._member(first, "conversion").value == "r"
assert self._member(first, "format_spec").value == ">5"
second = interpolations.elts[1]
assert self._member(second, "value").value == 1
assert self._member(second, "expression").value == "1"
assert self._member(second, "conversion").value is None
assert self._member(second, "format_spec").value == ""
@staticmethod
def test_template_string_plain_members() -> None:
node = builder.extract_node('t"just text"')
instance = next(node.infer())
strings = instance.getattr("strings")[0]
assert [elt.value for elt in strings.elts] == ["just text"]
assert instance.getattr("values")[0].elts == []
assert instance.getattr("interpolations")[0].elts == []
@staticmethod
def test_template_string_inference_template_class_missing() -> None:
# If ``string.templatelib`` does not provide the expected classes
# (e.g. the module failed to build), inference falls back to
# Uninferable instead of raising.
node = builder.extract_node('t"{1}"')
empty_module = astroid.parse("")
with unittest.mock.patch.dict(
astroid.MANAGER.astroid_cache, {"string.templatelib": empty_module}
):
assert list(node.infer()) == [Uninferable]
assert list(node.values[0].infer()) == [Uninferable]
@staticmethod
def test_template_string_inference_template_not_a_class() -> None:
# If the ``Template`` / ``Interpolation`` names do not resolve to
# classes, inference falls back to Uninferable instead of raising.
node = builder.extract_node('t"{1}"')
fake_module = astroid.parse("Template = 1\nInterpolation = 1")
with unittest.mock.patch.dict(
astroid.MANAGER.astroid_cache, {"string.templatelib": fake_module}
):
assert list(node.infer()) == [Uninferable]
assert list(node.values[0].infer()) == [Uninferable]
@pytest.mark.parametrize(
"node",
[
node
for node in astroid.nodes.ALL_NODE_CLASSES
if node.__name__ not in ["BaseContainer", "NodeNG", "const_factory"]
],
)
@pytest.mark.filterwarnings("error")
def test_str_repr_no_warnings(node):
parameters = inspect.signature(node.__init__).parameters
args = {}
for name, param_type in parameters.items():
if name == "self":
continue
if name == "parent" and "NodeNG" in param_type.annotation:
args[name] = SYNTHETIC_ROOT
elif "int" in param_type.annotation:
args[name] = random.randint(0, 50)
elif (
"NodeNG" in param_type.annotation
or "SuccessfulInferenceResult" in param_type.annotation
):
args[name] = UNATTACHED_UNKNOWN
elif "str" in param_type.annotation:
args[name] = ""
else:
args[name] = None
test_node = node(**args)
str(test_node)
repr(test_node)
def test_arguments_contains_all():
"""Ensure Arguments.arguments actually returns all available arguments"""
def manually_get_args(arg_node) -> set:
names = set()
if arg_node.args.vararg:
names.add(arg_node.args.vararg)
if arg_node.args.kwarg:
names.add(arg_node.args.kwarg)
names.update([x.name for x in arg_node.args.args])
names.update([x.name for x in arg_node.args.kwonlyargs])
return names
node = extract_node("""def a(fruit: str, *args, b=None, c=None, **kwargs): ...""")
assert manually_get_args(node) == {x.name for x in node.args.arguments}
node = extract_node("""def a(mango: int, b="banana", c=None, **kwargs): ...""")
assert manually_get_args(node) == {x.name for x in node.args.arguments}
node = extract_node("""def a(self, num = 10, *args): ...""")
assert manually_get_args(node) == {x.name for x in node.args.arguments}
def test_arguments_default_value():
node = extract_node(
"def fruit(eat='please', *, peel='no', trim='yes', **kwargs): ..."
)
assert node.args.default_value("eat").value == "please"
node = extract_node("def fruit(seeds, flavor='good', *, peel='maybe'): ...")
assert node.args.default_value("flavor").value == "good"
def test_arguments_annotations():
node = extract_node(
"def fruit(eat: str, /, peel: bool, *args: int, trim: float, **kwargs: bytes): ..."
)
assert isinstance(node.args, nodes.Arguments)
annotation_names = [
ann.name for ann in node.args.get_annotations() if isinstance(ann, nodes.Name)
]
assert all(
name in annotation_names for name in ("str", "bool", "int", "float", "bytes")
)
def test_deprecated_nodes_import_from_toplevel():
# pylint: disable=import-outside-toplevel,no-name-in-module
with pytest.raises(
DeprecationWarning, match="importing 'For' from 'astroid' is deprecated"
):
from astroid import For
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
from astroid import For
assert For is nodes.For
# This should not raise a DeprecationWarning
# pylint: disable-next=unused-import
from astroid import builtin_lookup
def test_str_long_name_no_crash() -> None:
"""str() should not crash with ValueError on nodes with long names.
Regression test for https://github.com/pylint-dev/astroid/issues/2764
"""
long_name = "a" * 200
code = f"class {long_name}:\n pass"
module = parse(code)
# This should not raise ValueError('width must be != 0')
result = str(module.body[0])
assert long_name in result
def test_str_large_int_no_crash() -> None:
"""str() should not crash with ValueError on nodes with large integer values.
Regression test for https://github.com/pylint-dev/astroid/issues/2785
"""
code = "x = 10 ** 5000"
module = parse(code)
# Infer the BinOp to get a Const with a huge int value
inferred = next(module.body[0].value.infer())
assert isinstance(inferred.value, int)
# This should not raise ValueError about integer string conversion limit
result = str(inferred)
assert "int" in result
def test_str_large_int_getitem_no_crash() -> None:
"""getitem error message should not crash with large integer values."""
code = "x = 10 ** 5000"
module = parse(code)
inferred = next(module.body[0].value.infer())
assert isinstance(inferred.value, int)
# Trigger the error path that formats the value
with pytest.raises(AstroidTypeError, match=r"too large to display|int"):
inferred.getitem(nodes.Const(0))
def test_slice_qname() -> None:
"""Test that Slice node has a qname method returning 'builtins.slice'."""
node = extract_node("f[1:2]")
assert isinstance(node, nodes.Subscript)
assert isinstance(node.slice, nodes.Slice)
assert node.slice.qname() == "builtins.slice"
@pytest.mark.skipif(not PY312_PLUS, reason="Uses 3.12 type param nodes")
@pytest.mark.parametrize(
"source,node_type,qname",
[
("class Basket[T]: pass", nodes.TypeVar, "typing.TypeVar"),
("def peel[**P](): pass", nodes.ParamSpec, "typing.ParamSpec"),
("def peel[*Ts](): pass", nodes.TypeVarTuple, "typing.TypeVarTuple"),
],
)
def test_type_param_qname(
source: str, node_type: type[nodes.NodeNG], qname: str
) -> None:
"""Type parameters are inferred as themselves, so they need a qname."""
node = extract_node(source)
type_param = node.type_params[0]
assert isinstance(type_param, node_type)
assert next(type_param.infer()) is type_param
assert type_param.qname() == qname
assert type_param.pytype() == qname
@pytest.mark.skipif(not PY312_PLUS, reason="Uses 3.12 type alias nodes")
def test_type_alias_qname() -> None:
"""A type alias is inferred as itself, so it needs a qname."""
node = extract_node("type Basket = int")
assert isinstance(node, nodes.TypeAlias)
assert next(node.infer()) is node
assert node.qname() == "typing.TypeAliasType"
assert node.pytype() == "typing.TypeAliasType"
def test_self_inferring_nodes_have_a_type() -> None:
"""A node inferred as itself is a value, so callers ask it for its type.
Without ``qname()`` and ``pytype()`` they crash with an ``AttributeError``
instead, as ``FunctionDef.decoratornames()`` used to for ``@T``. Nodes are
found by their ``_infer`` return annotation, so this only covers the usual
spelling, ``Iterator[TheNodeClass]``.
"""
node_classes = set()
to_visit = [nodes.NodeNG]
while to_visit:
for subclass in to_visit.pop().__subclasses__():
if subclass not in node_classes:
node_classes.add(subclass)
to_visit.append(subclass)
missing = []
for node_class in node_classes:
infer = node_class.__dict__.get("_infer")
if infer is None:
continue
returns = str(infer.__annotations__.get("return", ""))
if returns not in (f"Iterator[{node_class.__name__}]", "Iterator[Self]"):
continue
if issubclass(node_class, bases.Proxy):
# Proxies answer both from the class they proxy, e.g. Const.
continue
missing += [
f"{node_class.__name__}.{name}"
for name in ("qname", "pytype")
if not hasattr(node_class, name)
]
assert not missing