1+ """
2+ In order to retain a JavaScript-like event handler API, ReactPy allows users to configure event handlers with `event.debounce = <milliseconds>`.
3+ Implementing this syntax necessitates inspecting the bytecode of event handler functions.
4+ """
5+
16from __future__ import annotations
27
38import dis
@@ -76,11 +81,13 @@ def inspect_event_handler(func: Callable) -> tuple[bool, bool, int | None]:
7681 continue
7782
7883 # CPython 3.11+ may fuse ``LOAD_FAST value; LOAD_FAST target`` into
79- # the superinstruction ``LOAD_FAST_BORROW_LOAD_FAST_BORROW`` whose
80- # argval is ``(value_name, target_name)``. Treat it like a normal
81- # event load when the target matches.
84+ # a superinstruction whose argval is ``(value_name, target_name)``.
85+ # Two variants may appear depending on the interpreter:
86+ # * ``LOAD_FAST_LOAD_FAST`` (CPython 3.13+)
87+ # * ``LOAD_FAST_BORROW_LOAD_FAST_BORROW`` (CPython 3.14+)
88+ # Treat either as a normal event load when the target matches.
8289 if (
83- instr .opname == " LOAD_FAST_BORROW_LOAD_FAST_BORROW"
90+ instr .opname in ( "LOAD_FAST_LOAD_FAST" , " LOAD_FAST_BORROW_LOAD_FAST_BORROW")
8491 and isinstance (instr .argval , (list , tuple ))
8592 and len (instr .argval ) == _LOAD_FAST_BORROW_PAIR_SIZE
8693 and instr .argval [1 ] == event_arg_name
@@ -209,8 +216,11 @@ def _resolve_debounce_value(
209216 prev = instructions [store_index - 1 ]
210217
211218 # CPython 3.11+ superinstruction: argval is ``(value_name, target_name)``.
212- # The value comes from a function argument default.
213- if prev .opname == "LOAD_FAST_BORROW_LOAD_FAST_BORROW" :
219+ # The value comes from a function argument default. Two superinstructions
220+ # may appear here depending on the interpreter version:
221+ # * ``LOAD_FAST_LOAD_FAST`` (CPython 3.13+)
222+ # * ``LOAD_FAST_BORROW_LOAD_FAST_BORROW`` (CPython 3.14+)
223+ if prev .opname in ("LOAD_FAST_LOAD_FAST" , "LOAD_FAST_BORROW_LOAD_FAST_BORROW" ):
214224 names = prev .argval
215225 if isinstance (names , Iterable ) and not isinstance (names , str ):
216226 names = list (names )
@@ -226,7 +236,16 @@ def _resolve_debounce_value(
226236 val_instr = instructions [store_index - 2 ]
227237 target_instr = prev
228238
229- if target_instr .opname not in ("LOAD_FAST" , "LOAD_FAST_BORROW" ):
239+ if target_instr .opname not in (
240+ "LOAD_FAST" ,
241+ "LOAD_FAST_BORROW" ,
242+ # CPython 3.13+ may fuse two ``LOAD_FAST`` instructions into
243+ # ``LOAD_FAST_LOAD_FAST`` whose argval is ``(value_name, target_name)``.
244+ # The detector above already covers this case for debounce values,
245+ # but the standalone form can also appear if the value isn't a
246+ # default and the event is loaded separately.
247+ "LOAD_FAST_LOAD_FAST" ,
248+ ):
230249 return None
231250 if target_instr .argval != event_arg_name :
232251 return None
0 commit comments