Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/debugpy/_vendored/pydevd/pydevd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 == "<string>":
# Special handling for '<string>'
if file_type == self.LIB_FILE:
Expand Down
53 changes: 52 additions & 1 deletion tests/debugpy/test_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Loading