diff --git a/astroid/brain/brain_dataclasses.py b/astroid/brain/brain_dataclasses.py index 88a4385f..743dfe96 100644 --- a/astroid/brain/brain_dataclasses.py +++ b/astroid/brain/brain_dataclasses.py @@ -19,7 +19,7 @@ from typing import Literal, Tuple, Union from astroid import bases, context, nodes from astroid.builder import parse -from astroid.const import PY39_PLUS, PY310_PLUS +from astroid.const import PY39_PLUS, PY310_PLUS, PY313_PLUS from astroid.exceptions import AstroidSyntaxError, InferenceError, UseInferenceDefault from astroid.inference_tip import inference_tip from astroid.manager import AstroidManager @@ -503,6 +503,15 @@ def _looks_like_dataclass_field_call( return inferred.name == FIELD_NAME and inferred.root().name in DATACLASS_MODULES +def _looks_like_dataclasses(node: nodes.Module) -> bool: + return node.qname() == "dataclasses" + + +def _resolve_private_replace_to_public(node: nodes.Module) -> None: + if "_replace" in node.locals: + node.locals["replace"] = node.locals["_replace"] + + def _get_field_default(field_call: nodes.Call) -> _FieldDefaultReturn: """Return a the default value of a field call, and the corresponding keyword argument name. @@ -618,6 +627,13 @@ def _infer_instance_from_annotation( def register(manager: AstroidManager) -> None: + if PY313_PLUS: + manager.register_transform( + nodes.Module, + _resolve_private_replace_to_public, + _looks_like_dataclasses, + ) + manager.register_transform( nodes.ClassDef, dataclass_transform, is_decorated_with_dataclass ) diff --git a/astroid/brain/brain_pathlib.py b/astroid/brain/brain_pathlib.py index 116cd2ee..d0f53132 100644 --- a/astroid/brain/brain_pathlib.py +++ b/astroid/brain/brain_pathlib.py @@ -8,6 +8,7 @@ from collections.abc import Iterator from astroid import bases, context, inference_tip, nodes from astroid.builder import _extract_single_node +from astroid.const import PY313_PLUS from astroid.exceptions import InferenceError, UseInferenceDefault from astroid.manager import AstroidManager @@ -27,10 +28,11 @@ def _looks_like_parents_subscript(node: nodes.Subscript) -> bool: value = next(node.value.infer()) except (InferenceError, StopIteration): return False + parents = "builtins.tuple" if PY313_PLUS else "pathlib._PathParents" return ( isinstance(value, bases.Instance) and isinstance(value._proxied, nodes.ClassDef) - and value.qname() == "pathlib._PathParents" + and value.qname() == parents ) diff --git a/astroid/brain/brain_typing.py b/astroid/brain/brain_typing.py index 9965abc2..7a7c7183 100644 --- a/astroid/brain/brain_typing.py +++ b/astroid/brain/brain_typing.py @@ -15,7 +15,7 @@ from typing import Final from astroid import context, extract_node, inference_tip from astroid.brain.helpers import register_module_extender from astroid.builder import AstroidBuilder, _extract_single_node -from astroid.const import PY39_PLUS, PY312_PLUS +from astroid.const import PY39_PLUS, PY312_PLUS, PY313_PLUS from astroid.exceptions import ( AstroidSyntaxError, AttributeInferenceError, @@ -168,6 +168,15 @@ def infer_typing_attr( # If typing subscript belongs to an alias handle it separately. raise UseInferenceDefault + if ( + PY313_PLUS + and isinstance(value, FunctionDef) + and value.qname() == "typing.Annotated" + ): + # typing.Annotated is a FunctionDef on 3.13+ + node._explicit_inference = lambda node, context: iter([value]) + return iter([value]) + if isinstance(value, ClassDef) and value.qname() in { "typing.Generic", "typing.Annotated", diff --git a/tests/brain/test_brain.py b/tests/brain/test_brain.py index b8bc84e3..78b6f8b3 100644 --- a/tests/brain/test_brain.py +++ b/tests/brain/test_brain.py @@ -647,7 +647,7 @@ class TypingBrain(unittest.TestCase): @test_utils.require_version(minver="3.9") def test_typing_annotated_subscriptable(self): - """Test typing.Annotated is subscriptable with __class_getitem__""" + """typing.Annotated is subscriptable with __class_getitem__ below 3.13.""" node = builder.extract_node( """ import typing @@ -655,8 +655,13 @@ class TypingBrain(unittest.TestCase): """ ) inferred = next(node.infer()) - assert isinstance(inferred, nodes.ClassDef) - assert isinstance(inferred.getattr("__class_getitem__")[0], nodes.FunctionDef) + if PY313_PLUS: + assert isinstance(inferred, nodes.FunctionDef) + else: + assert isinstance(inferred, nodes.ClassDef) + assert isinstance( + inferred.getattr("__class_getitem__")[0], nodes.FunctionDef + ) def test_typing_generic_slots(self): """Test slots for Generic subclass.""" diff --git a/tests/brain/test_pathlib.py b/tests/brain/test_pathlib.py index d935d964..5aea8d37 100644 --- a/tests/brain/test_pathlib.py +++ b/tests/brain/test_pathlib.py @@ -5,7 +5,7 @@ import astroid from astroid import bases -from astroid.const import PY310_PLUS +from astroid.const import PY310_PLUS, PY313_PLUS from astroid.util import Uninferable @@ -23,7 +23,10 @@ def test_inference_parents() -> None: inferred = name_node.inferred() assert len(inferred) == 1 assert isinstance(inferred[0], bases.Instance) - assert inferred[0].qname() == "pathlib._PathParents" + if PY313_PLUS: + assert inferred[0].qname() == "builtins.tuple" + else: + assert inferred[0].qname() == "pathlib._PathParents" def test_inference_parents_subscript_index() -> None: @@ -40,7 +43,10 @@ def test_inference_parents_subscript_index() -> None: inferred = path.inferred() assert len(inferred) == 1 assert isinstance(inferred[0], bases.Instance) - assert inferred[0].qname() == "pathlib.Path" + if PY313_PLUS: + assert inferred[0].qname() == "pathlib._local.Path" + else: + assert inferred[0].qname() == "pathlib.Path" def test_inference_parents_subscript_slice() -> None: diff --git a/tests/test_inference.py b/tests/test_inference.py index ec8fc71b..95f00821 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -4458,8 +4458,7 @@ class InferenceTest(resources.SysPathSetup, unittest.TestCase): # and reraise it as a TypeError in Class.getitem node = extract_node( """ - def test(): - yield + def test(): ... test() """ )