Repository: python/cpython (Python)

Issue:
Annotated with forward references does not partially evaluate
# Bug report

### Bug description:

I ran into this while writing tests for #135228 with additional ways to unintentionally make the old class stick around. 

MRE:

```python
from typing import Annotated
from annotationlib import get_annotations, Format

def f(x: Annotated[undefined, '']): pass

annos = get_annotations(f, format=Format.FORWARDREF)
print(annos['x'])
```

Expected: `typing.Annotated[ForwardRef('undefined', owner=...), '']`
Actual: `ForwardRef("Annotated[undefined, '']", owner=...)`

---

The cause is this:

* `_Stringifier` has a `__getattr__` that will return a new `_Stringifier`
* Annotated checks `_is_unpacked_typevartuple` to determine if it should fail
* `_is_unpacked_typevartuple` checks `getattr(x, "__typing_is_unpacked_typevartuple__", False)`
* This returns a new `_Stringifier` which is truthy
* Annotated raises a TypeError as it thinks the `_Stringifier` is an unpacked typevartuple
* `call_annotate_function` falls back to the all-stringifiers backup mode which makes the whole thing a `ForwardRef`

I think the solution for `Annotated` not partially evaluating is probably to make `typing._is_unpacked_typevartuple` check that the value is `True` rather than truthy.

---

One reason why this might matter - the exception that occurs internally can also prevent other annotations from partially evaluating:

```python
from typing import Annotated
from dataclasses import InitVar, dataclass, fields
from pprint import pp

@dataclass
class PartiallyEvaluates:
    x: InitVar[undefined]

@dataclass
class NoLongerEvaluates:
    x: InitVar[undefined]
    y: Annotated[undefined, '']
```

The addition of the `Annotated` field makes it so `x` is no longer correctly identified as an InitVar here.

[^1]: And how I discovered this initially.

### CPython versions tested on:

3.14, CPython main branch

### Operating systems tested on:

_No response_

<!-- gh-linked-prs -->
### Linked PRs
* gh-137712
* gh-138574
<!-- /gh-linked-prs -->



Discussion:
Nice catch. Yes, checking for `is True` sounds like the right solution. 



Patch:
diff --git a/Lib/typing.py b/Lib/typing.py
index 8c1d265019bb94..4191fbdda7d6cd 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1033,8 +1033,10 @@ def evaluate_forward_ref(
 
 
 def _is_unpacked_typevartuple(x: Any) -> bool:
+    # Need to check 'is True' here
+    # See: https://github.com/python/cpython/issues/137706
     return ((not isinstance(x, type)) and
-            getattr(x, '__typing_is_unpacked_typevartuple__', False))
+            getattr(x, '__typing_is_unpacked_typevartuple__', False) is True)
 
 
 def _is_typevar_like(x: Any) -> bool:
diff --git a/Misc/NEWS.d/next/Library/2025-09-05-21-10-24.gh-issue-137706.0EztiJ.rst b/Misc/NEWS.d/next/Library/2025-09-05-21-10-24.gh-issue-137706.0EztiJ.rst
new file mode 100644
index 00000000000000..9eed50ec20a8f6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-09-05-21-10-24.gh-issue-137706.0EztiJ.rst
@@ -0,0 +1,1 @@
+Fix the partial evaluation of annotations that use ``typing.Annotated[T, x]`` where ``T`` is a forward reference.


Test patch:
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 6317d4657619f0..0fc5415c390145 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -9788,6 +9788,19 @@ class B(str): ...
         self.assertIs(type(field_c2.__metadata__[0]), float)
         self.assertIs(type(field_c3.__metadata__[0]), bool)
 
+    def test_forwardref_partial_evaluation(self):
+        # Test that Annotated partially evaluates if it contains a ForwardRef
+        # See: https://github.com/python/cpython/issues/137706
+        def f(x: Annotated[undefined, '']): pass
+
+        ann = annotationlib.get_annotations(f, format=annotationlib.Format.FORWARDREF)
+
+        # Test that the attributes are retrievable from the partially evaluated annotation
+        x_ann = ann['x']
+        self.assertIs(get_origin(x_ann), Annotated)
+        self.assertEqual(x_ann.__origin__, EqualToForwardRef('undefined', owner=f))
+        self.assertEqual(x_ann.__metadata__, ('',))
+
 
 class TypeAliasTests(BaseTestCase):
     def test_canonical_usage_with_variable_annotation(self):