diff --git a/src/debugpy/_vendored/pydevd/pydevd.py b/src/debugpy/_vendored/pydevd/pydevd.py index 4abba2e1..3b2716fe 100644 --- a/src/debugpy/_vendored/pydevd/pydevd.py +++ b/src/debugpy/_vendored/pydevd/pydevd.py @@ -93,6 +93,7 @@ PYDEVD_IPYTHON_COMPATIBLE_DEBUGGING, PYDEVD_IPYTHON_CONTEXT, PYDEVD_USE_SYS_MONITORING, + IS_PY314_OR_GREATER, ) from _pydevd_bundle.pydevd_defaults import PydevdCustomization # Note: import alias used on pydev_monkey. from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init @@ -1292,6 +1293,16 @@ def in_project_scope(self, frame, absolute_filename=None): if file_type == self.PYDEV_FILE: cache[cache_key] = False + elif IS_PY314_OR_GREATER and frame.f_code.co_name == "__annotate__": + # Special handling for __annotate__ functions (PEP 649 in Python 3.14+). + # These are compiler-generated functions that can raise NotImplementedError + # when called with unsupported format arguments by inspect.call_annotate_function. + # They should be treated as library code to avoid false positives in exception handling. + # Note: PEP 649 reserves the __annotate__ name for compiler-generated functions, + # so user-defined functions with this name are discouraged and will also be treated + # as library code to maintain consistency with the language design. + cache[cache_key] = False + elif absolute_filename == "": # Special handling for '' if file_type == self.LIB_FILE: diff --git a/tests/debugpy/test_exception.py b/tests/debugpy/test_exception.py index 76412346..bf21c9dd 100644 --- a/tests/debugpy/test_exception.py +++ b/tests/debugpy/test_exception.py @@ -5,7 +5,7 @@ import pytest import sys -from _pydevd_bundle.pydevd_constants import IS_PY312_OR_GREATER +from _pydevd_bundle.pydevd_constants import IS_PY312_OR_GREATER, IS_PY314_OR_GREATER from tests import debug from tests.debug import runners, targets from tests.patterns import some @@ -390,3 +390,54 @@ def do_something2(n): assert min_expected_lines <= stack_line_count <= max_expected_lines session.request_continue() + + +@pytest.mark.skipif(not IS_PY314_OR_GREATER, reason="Test requires Python 3.14+") +def test_annotate_function_not_treated_as_user_exception(pyfile, target, run): + """ + Test that __annotate__ functions (PEP 649) are treated as library code. + In Python 3.14+, compiler-generated __annotate__ functions can raise + NotImplementedError when called by inspect.call_annotate_function with + unsupported format arguments. These should not be reported as user exceptions. + """ + @pyfile + def code_to_debug(): + import debuggee + from typing import get_type_hints + + debuggee.setup() + + # Define a class with annotations that will trigger __annotate__ function generation + class AnnotatedClass: + value: int = 42 + name: str = "test" + + # This will trigger the __annotate__ function to be called by the runtime + # which may raise NotImplementedError internally (expected behavior) + try: + hints = get_type_hints(AnnotatedClass) + print(f"Type hints: {hints}") # @bp + except Exception as e: + print(f"Exception: {e}") + + with debug.Session() as session: + session.config["justMyCode"] = True + + with run(session, target(code_to_debug)): + # Set exception breakpoints for user uncaught exceptions + session.request( + "setExceptionBreakpoints", + {"filters": ["userUnhandled"]} + ) + session.set_breakpoints(code_to_debug, all) + + # Wait for the breakpoint + session.wait_for_stop( + "breakpoint", + expected_frames=[some.dap.frame(code_to_debug, "bp")] + ) + + # The test passes if we reach here without stopping on a NotImplementedError + # from __annotate__ function + session.request_continue() +