From 4d69ef2c6fe6a01bd9d1a97d07ad66ee87dfaa44 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 17:30:03 +0000 Subject: [PATCH 1/6] Initial plan From 4a9f946732cbbcc880e029128fa1bc6812cfd148 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 17:36:43 +0000 Subject: [PATCH 2/6] Add special handling for __annotate__ functions in Python 3.14+ Co-authored-by: rchiodo <19672699+rchiodo@users.noreply.github.com> --- src/debugpy/_vendored/pydevd/pydevd.py | 7 ++++ tests/debugpy/test_exception.py | 53 +++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/debugpy/_vendored/pydevd/pydevd.py b/src/debugpy/_vendored/pydevd/pydevd.py index 4abba2e15..9079978db 100644 --- a/src/debugpy/_vendored/pydevd/pydevd.py +++ b/src/debugpy/_vendored/pydevd/pydevd.py @@ -1292,6 +1292,13 @@ def in_project_scope(self, frame, absolute_filename=None): if file_type == self.PYDEV_FILE: cache[cache_key] = False + elif 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. + 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 764123465..f2ce0d7c0 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 + stop = 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() + From a09aceab067cd434f34f4162a484bee5b670ecad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 17:40:50 +0000 Subject: [PATCH 3/6] Add Python version check for __annotate__ special handling Co-authored-by: rchiodo <19672699+rchiodo@users.noreply.github.com> --- src/debugpy/_vendored/pydevd/pydevd.py | 3 ++- tests/debugpy/test_exception.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/pydevd.py b/src/debugpy/_vendored/pydevd/pydevd.py index 9079978db..36e54f53e 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,7 +1293,7 @@ def in_project_scope(self, frame, absolute_filename=None): if file_type == self.PYDEV_FILE: cache[cache_key] = False - elif frame.f_code.co_name == "__annotate__": + 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. diff --git a/tests/debugpy/test_exception.py b/tests/debugpy/test_exception.py index f2ce0d7c0..e78dfe688 100644 --- a/tests/debugpy/test_exception.py +++ b/tests/debugpy/test_exception.py @@ -422,7 +422,7 @@ class AnnotatedClass: 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( From b1772883bc121a2d1bef94f0eb720c8d5c338c75 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 17:41:45 +0000 Subject: [PATCH 4/6] Fix remaining trailing whitespace Co-authored-by: rchiodo <19672699+rchiodo@users.noreply.github.com> --- tests/debugpy/test_exception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/debugpy/test_exception.py b/tests/debugpy/test_exception.py index e78dfe688..51a3053e0 100644 --- a/tests/debugpy/test_exception.py +++ b/tests/debugpy/test_exception.py @@ -436,7 +436,7 @@ class AnnotatedClass: "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() From 6c3bcc9e8faa5303a445f96745f4f2e30b354a0f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 17:44:15 +0000 Subject: [PATCH 5/6] Add clarifying comment about __annotate__ name reservation Co-authored-by: rchiodo <19672699+rchiodo@users.noreply.github.com> --- src/debugpy/_vendored/pydevd/pydevd.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/debugpy/_vendored/pydevd/pydevd.py b/src/debugpy/_vendored/pydevd/pydevd.py index 36e54f53e..3b2716fe9 100644 --- a/src/debugpy/_vendored/pydevd/pydevd.py +++ b/src/debugpy/_vendored/pydevd/pydevd.py @@ -1298,6 +1298,9 @@ def in_project_scope(self, frame, absolute_filename=None): # 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 == "": From 4cce4a6f7326f6adaaffb9a4b3af96058e33193b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 18:19:37 +0000 Subject: [PATCH 6/6] Fix ruff error: remove unused 'stop' variable in test Co-authored-by: rchiodo <19672699+rchiodo@users.noreply.github.com> --- tests/debugpy/test_exception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/debugpy/test_exception.py b/tests/debugpy/test_exception.py index 51a3053e0..bf21c9dd7 100644 --- a/tests/debugpy/test_exception.py +++ b/tests/debugpy/test_exception.py @@ -432,7 +432,7 @@ class AnnotatedClass: session.set_breakpoints(code_to_debug, all) # Wait for the breakpoint - stop = session.wait_for_stop( + session.wait_for_stop( "breakpoint", expected_frames=[some.dap.frame(code_to_debug, "bp")] )