Skip to content

Conversation

@Zac-HD
Copy link
Member

@Zac-HD Zac-HD commented Dec 1, 2025

Add support for self-referential type aliases like A = list[Union["A", str]] in from_type(). Previously such recursive types would raise ResolutionFailed, but now Hypothesis can resolve them by looking up forward references in the caller's namespace.

Fixes #4542

@Liam-DeVoe
Copy link
Member

(just noting that I'm very skeptical of this one at a glance, and would like to review before merge)

@Zac-HD Zac-HD requested a review from Liam-DeVoe December 1, 2025 05:29
Add support for self-referential type aliases like `A = list[Union["A", str]]`
in from_type(). Previously such recursive types would raise ResolutionFailed,
but now Hypothesis can resolve them by looking up forward references in the
caller's namespace.

Fixes HypothesisWorks#4542
With the new forward reference resolution feature (HypothesisWorks#4542), forward
references like Optional["ConcreteFoo"] now resolve successfully when
ConcreteFoo is in scope. Updated tests accordingly:

- Split test_cannot_resolve_bare_forward_reference into two tests:
  - test_can_resolve_forward_reference_to_class for Optional/list/List
  - test_cannot_resolve_type_forward_reference for type/Type
- Changed test_string_forward_ref_message to test_string_forward_ref_resolved
  since User with list["User"] now works on all Python versions
- Add return type annotation (typing.Any)
- Add proper type annotation for frame variable
- Import types module for FrameType
@Zac-HD Zac-HD force-pushed the claude/fix-hypothesis-4542-01WVZEUisPpFAyKj8s4K421Z branch from bddbc01 to a6200aa Compare December 1, 2025 05:37
Explicitly tests that forward references can be resolved from
local variables (f_locals path in _resolve_forward_ref_in_caller).
Move test_forward_ref_resolved_from_local_scope from nocover to cover
directory so it's included in coverage runs and covers the f_locals
lookup path in _resolve_forward_ref_in_caller.
@Liam-DeVoe
Copy link
Member

I'm worried about false positives / namespace collisions here. If we stack walk looking for Foo, we might find an unrelated frame which defines Foo and use that value during resolution. We might think to mitigate this by removing hypothesis frames, but a type could still pass through arbitrary many user frames, from definition to from_type.

We want to find specifically the stack where the forward ref / type was defined. I'm not sure how to do that.

This feels like something other libraries (pydantic / type checkers) have solved? I wonder how they approach this?

@Liam-DeVoe
Copy link
Member

Liam-DeVoe commented Dec 8, 2025

some thoughts from an offline discussion between myself and Zac: we could perform this stack-walking only when it produces an unambiguous value in all frames. The downside is confused users when this breaks after a seemingly unrelated user code change.

@jobh
Copy link
Contributor

jobh commented Dec 8, 2025

some thoughts from an offline discussion between myself and Zac: we could perform this stack-walking only when it produces an unambiguous value in all frames. The downside is confused users when this breaks after a seemingly unrelated user code change.

I don't think that sounds too bad, if we can provide an explanation and concrete guidance when it happens. I don't know what that guidance should be - wrap in typing.ForwardRef and pre-resolve using typing._eval_type(..., globals(), locals()) before passing to from_type?

To avoid false positives from namespace collisions, the stack-walking
forward reference resolution now checks all frames and only returns a
value if all frames that define the name have the same value.

If different frames have different values for the same name, resolution
returns None (ambiguous) and falls back to normal error handling.

Added test for ambiguous forward reference case.
@Liam-DeVoe
Copy link
Member

Agreed that we need some actionable advice to give to users in the failure case 👍

On 3.14+, we could advise the user to instantiate ForwardRef with either the owner= or globals / locals parameter: https://docs.python.org/3/library/annotationlib.html#annotationlib.ForwardRef

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support recursive forward references in st.from_type

4 participants