Skip to content

Type and project Python delegate callbacks, including MapChanged handlers - #191

Open
leileizhang (lei9444) wants to merge 11 commits into
mainfrom
lei9444-typed-python-mapchanged-handlers
Open

leileizhang (lei9444) wants to merge 11 commits into
mainfrom
lei9444-typed-python-mapchanged-handlers

Conversation

@lei9444

@lei9444 leileizhang (lei9444) commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Problem

  • MapChanged handlers were typed as Callable[..., object], and received raw DynWinRTValue arguments that did not expose collection_change or key.
  • Python callback typing and projection were special-cased by delegate name, leaving bespoke events and other delegate inputs inconsistent.

Key changes

  • Derive callback annotations and argument adapters from each delegate's Invoke signature, with generic arguments substituted.
  • Apply the adapter to instance/static events, callback parameters, and delegate-typed properties through the shared delegate-input path.
  • Project IObservableMap<K,V> over IMap<K,V> so MapChanged senders implement Python's mutable-mapping protocol.
  • Pass DynWinRtDelegate, DynWinRTValue, and wrappers with _obj through unchanged on on_*, subscribe_*, static events, and callback parameters; reject them clearly from once_*.
  • Collect callback structs and type dependencies for every generated module, and validate generated Python names, imports, facades, and lazy symbols in E2E.

Notes

  • Python callables now receive projected callback arguments; explicit native delegates continue receiving raw DynWinRTValue arguments.
  • Native delegate objects work in constructors; raw delegate values should use named factories because one raw constructor value is reserved for native-instance wrapping.
  • Async-operation callback arguments remain raw so projection cannot take over their completion lifecycle.
  • Callback reference annotations are optimistically non-null except for WinRT Object and IReference<T>; WinMD has no nullability metadata and runtime nulls still become None.

Collect the delegate callable annotation, event callback adapter, and delegate input conversion in one module so they can be derived from delegate signatures. Generated output is unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Python callables are converted to delegates in one place. The callback annotation and the projection of each native argument now come from the delegate's Invoke signature, with generic arguments substituted, instead of per-name branches for TypedEventHandler, EventHandler, and VectorChangedEventHandler. The same adapter now applies to instance events, static events, callback parameters, and delegate-typed properties through _dynwinrt_delegate(value, iid, types, project); an existing DynWinRTValue delegate still passes through unchanged.

Callback arguments are annotated non-null except WinRT Object and IReference<T>, through a single py_delegate_argument_type hook. Modules import the types named by those annotations from the delegate signatures, which replaces the IObservableVector-specific import blocks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Like IObservableVector<T> over IVector<T>, the Python IObservableMap<K, V> wrapper now extends the IMap<K, V> projection, keeps its own interface for event registration, and its stub is a MutableMapping. MapChanged handlers therefore receive a sender that supports len(), indexing, membership, and iteration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add Python E2E checks that PropertySet and StringMap map_changed handlers receive the observable map and IMapChangedEventArgs for inserts, updates, and removals, and that ThreadPool.run_async and ThreadPoolTimer.create_timer handlers receive projected IAsyncAction and ThreadPoolTimer arguments. Strict mypy consumers cover the handler annotations.

The async cancellation E2E now passes an explicit native delegate, which still receives raw arguments, so its work item can poll IAsyncInfo.Status. Document the projected callback arguments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Mixed-language test coverage

Workflow status: ✅ Passed

Layer Lines Functions Branches/regions
Rust, including native .pyd/.node 86.48% 81.73% 86.14% regions
Python aggregate 70.16% n/a 36.57% branches
Python runtime 99.12% n/a 97.37% branches
Generated Python WinRT projections 68.92% n/a 26.52% branches
Generated Python WinRT implementations 72.39% n/a 47.07% branches
JavaScript aggregate 21.91% 25.18% 57.61% branches
JavaScript runtime 44.27% 45.76% 78.99% branches
Generated WinRT projections 22.8% 18.7% 54.84% branches
Generated WinRT implementations 45.99% 59.19% 60.97% branches
Generated Classic COM projections 11.88% 23.97% 53.4% branches

View workflow run and download full HTML/LCOV/XML reports

Route on_ and subscribe_ through the shared delegate-input helper so DynWinRtDelegate objects, DynWinRTValue delegate values, and wrappers with _obj register unchanged. once_ remains callable-only and raises a clear TypeError for native delegates.

Use positional names for internal projection lambdas to prevent collisions after Python name normalization, and keep async-operation callback arguments raw so projection cannot take over their completion.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Include delegate Invoke parameters when collecting structs and other callback annotation dependencies for runtime-class modules, including static and factory interfaces. Add missing runtime-class IID constants for caller-filled arrays.

The synthetic struct-event regression generates a class module and executes its extracted adapter, and callback integration checks cover events, static events, callback parameters, setters, async ownership, and colliding metadata names.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Exercise native delegate objects and values through on_, subscribe_, static events, and ThreadPool callbacks, and verify once_ rejects native delegates clearly. Document optimistic callback nullability and the raw async-operation exception.

Add a generated Python analyzer that checks compilation, global name resolution in every scope, relative imports, lazy symbols, facades, and stubs. Run it in Python E2E; corpus validation covers all 328 Windows namespaces.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Verify one thousand projected MapChanged deliveries do not grow the active projected lifetime scope and that a retained sender/event argument stays valid after the scope closes. Document ownership and deterministic release.

Run async cancellation through the generated ThreadPool.run_async callable path, keeping its IAsyncAction raw and inspecting cancellation through generated IAsyncInfo as documented.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Treat DynWinRtDelegate as a valid delegate input in constructor and overload guards. Constructor stubs intentionally omit raw DynWinRTValue delegate inputs because the single-value native-wrapper shortcut claims them before constructor dispatch.

Execute generated constructor and overload dispatch with native delegate objects, and cover additional generated Gamepad value/event paths to preserve Python coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.

1 participant