@@ -79,23 +79,39 @@ def restore(
7979_explicit_scope_stack : contextvars .ContextVar [tuple [dict [str , object ] | None , ...]] = (
8080 contextvars .ContextVar ("_explicit_scope_stack" , default = ())
8181)
82+ _explicit_owner_stack : contextvars .ContextVar [tuple [types .CodeType | None , ...]] = (
83+ contextvars .ContextVar ("_explicit_owner_stack" , default = ())
84+ )
8285
8386
8487def push_memo (
85- memo : ShapeMemo | None = None , * , scope : dict [str , object ] | None = None
88+ memo : ShapeMemo | None = None ,
89+ * ,
90+ scope : dict [str , object ] | None = None ,
91+ owner_code : types .CodeType | None = None ,
8692) -> ShapeMemo :
87- """Push a memo onto the explicit stack. Pair with :func:`pop_memo`."""
93+ """Push a memo onto the explicit stack. Pair with :func:`pop_memo`.
94+
95+ Parameters
96+ ----------
97+ owner_code:
98+ When set, this entry is only visible to frames whose ``f_code``
99+ matches. Untagged entries (``None``) are unconditional and used
100+ by :class:`check_context` and :class:`_TreeChecker`.
101+ """
88102 if memo is None :
89103 memo = ShapeMemo ()
90104 _explicit_stack .set (_explicit_stack .get () + (memo ,))
91105 _explicit_scope_stack .set (_explicit_scope_stack .get () + (scope ,))
106+ _explicit_owner_stack .set (_explicit_owner_stack .get () + (owner_code ,))
92107 return memo
93108
94109
95110def pop_memo () -> None :
96111 """Pop the most recent explicit memo."""
97112 _explicit_stack .set (_explicit_stack .get ()[:- 1 ])
98113 _explicit_scope_stack .set (_explicit_scope_stack .get ()[:- 1 ])
114+ _explicit_owner_stack .set (_explicit_owner_stack .get ()[:- 1 ])
99115
100116
101117# ---------------------------------------------------------------------------
@@ -122,10 +138,22 @@ def get_memo(_depth: int = 2) -> ShapeMemo:
122138 Default ``2`` accounts for: our validator → beartype's ``_is_valid_bool``
123139 → beartype wrapper.
124140 """
125- # 1. Explicit stack takes priority
141+ # 1. Explicit stack takes priority (if owner matches or untagged)
126142 explicit = _explicit_stack .get ()
127143 if explicit :
128- return explicit [- 1 ]
144+ owners = _explicit_owner_stack .get ()
145+ owner_code = owners [- 1 ] if owners else None
146+ if owner_code is None :
147+ # Untagged entry (check_context / TreeChecker) — always visible
148+ return explicit [- 1 ]
149+ # Tagged entry — only visible if the caller's frame matches
150+ try :
151+ frame = sys ._getframe (_depth )
152+ if frame .f_code is owner_code :
153+ return explicit [- 1 ]
154+ except ValueError :
155+ pass
156+ # Fall through to frame-based detection
129157
130158 # 2. Frame-based detection
131159 try :
@@ -187,7 +215,19 @@ def get_scope(_depth: int = 2) -> dict[str, object]:
187215 """Return the current runtime scope for ``Value(...)`` expressions."""
188216 explicit = _explicit_scope_stack .get ()
189217 if explicit and explicit [- 1 ] is not None :
190- return explicit [- 1 ]
218+ owners = _explicit_owner_stack .get ()
219+ owner_code = owners [- 1 ] if owners else None
220+ if owner_code is None :
221+ # Untagged entry — always visible
222+ return explicit [- 1 ]
223+ # Tagged entry — only visible if the caller's frame matches
224+ try :
225+ frame = sys ._getframe (_depth )
226+ if frame .f_code is owner_code :
227+ return explicit [- 1 ]
228+ except ValueError :
229+ pass
230+ # Fall through to frame-based detection
191231
192232 try :
193233 frame = sys ._getframe (_depth )
0 commit comments