Skip to content

Fix Python overload naming and interface-parameter dispatch - #193

Merged
leileizhang (lei9444) merged 20 commits into
mainfrom
lei9444-fix-python-overload-naming-and-dispatch
Sep 29, 2026
Merged

leileizhang (lei9444) merged 20 commits into
mainfrom
lei9444-fix-python-overload-naming-and-dispatch

Conversation

@lei9444

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

Copy link
Copy Markdown
Contributor

Problem

  • Python WinRT overloads were grouped by [Overload] ABI names and suffix guesses, leaving documented CLR names missing or split (for example, StorageFile.copy_async, ToastNotifier.update, and DecimalFormatter.format).
  • isinstance guards rejected runtime-class objects passed to interface-typed overload parameters; DataWriter(InMemoryRandomAccessStream()) failed with "No matching constructor".

Key changes

  • One member plan now drives public names, candidate order, private targets, aliases, and collisions in both generated .py and .pyi. The preparatory refactor alone left output byte-identical.
  • Dispatch keeps existing exact matches first, then checks interface support with QueryInterface. Previously successful calls retain their exact native targets.
  • Documented CLR names and previous ABI names are present for all 623/623 SDK overload groups. Former standalone names keep their exact target; implementation-handler (*Handlers) names are unchanged.
  • Five groups preserve their old ABI grouping because the documented name collides with another member. Separately, ThreadPool.run_async(handler) intentionally stays single-argument to retain mypy callback inference; priority/options calls remain run_with_priority_async and run_with_priority_and_options_async.

Notes

  • 621/623 groups are fully merged under the documented name. The two exceptions are the intentional ThreadPool.RunAsync ABI grouping and deduplication of identical Python INumberFormatter.Format stub signatures.
  • The SDK comparison adds 455 public names and removes none. Stubs retain overloads with matching parameters but different return types using a targeted # type: ignore[overload-cannot-match].

Overload groups, public method names, private dispatch names, and
compatibility aliases were recomputed ad hoc at every call site:
`method_group_key` ran with different name sets for statics, instance
methods, constructors, required-interface wrappers, interface modules, and
stubs; `generate_*_method_group` re-derived the public name from the group
alone; constructors regrouped all statics to recover private names; and the
runtime `.py` and `.pyi` generators regrouped independently. Every rule change
had to be repeated in all of those places.

Add `member_plan`, which computes a `ScopePlan` (ordered candidates with
their private attribute names, group names, and aliases) once per generated
Python class, and render both the runtime and stub output from it.
Generators keep their established member order and emit a group at its first
method, so the generated output does not change.

Overload dispatch is emitted by one `emit_dispatch` for instance, static, and
constructor (`__new__`/`__init__`) dispatchers. Each parameter now has a
structured `ParamGuard` with a strict guard and an optional permissive guard
for a second pass that runs only after every strict guard failed. No guard is
permissive yet.

Verified: `.py`/`.pyi` generated for all 142 Windows.winmd namespaces are
byte-identical to the previous generator.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Generated runtime-class wrappers do not inherit interface wrapper classes, so
the `isinstance(x, IFoo)` guard of a known interface parameter rejected every
runtime-class object in overload dispatch. For example,
`DataWriter(InMemoryRandomAccessStream())` and `DataReader(stream)` raised
`TypeError: No matching constructor` even though the stubs accept them.

Give known interface parameters a permissive guard that also accepts any
projected object or raw DynWinRTValue supporting the interface through
QueryInterface, while keeping the `isinstance` check for interface wrappers
and Python implementations. Permissive guards only run in the dispatcher's
second pass, after every candidate's strict guards failed, so calls that
dispatch today keep their overload and only calls that raised TypeError can
now match. This covers constructors, static and factory methods, instance
methods, and interface wrappers.

Interface IID constants (`IID_ARG_<Namespace>_<Name>`) are emitted locally,
like the runtime-class ones, so guards do not import interface modules at
call time and also work for interfaces that are not generated. IID constants
are now collected from every wrapped parameter, including FillArray buffers,
which fixes an undefined `IID_ARG_*` reference in
`IIterator<RuntimeClass>.GetMany`.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
WinMD gives all overloads of a method one CLR/MethodDef name and assigns each
ABI slot a unique `[Overload]` name. Python grouped on those unique ABI names,
then used suffix heuristics only when a base ABI name happened to exist. As a
result, hundreds of documented Python methods were incomplete or missing:
`StorageFile.CopyAsync` became three `copy_overload*` methods, for example.

Use each non-accessor method's snake-cased `raw_name` as the member plan's
primary key, with the established suffix heuristics on top. Accessors and
events keep their previous names. Static and instance scopes are planned
together so CLR names that collide with a property, event helper, generated
member, the other scope, or an existing name with different behavior fall
back to the prior name.

All old method names remain. A former standalone ABI method aliases its exact
private implementation, preserving its guard-free behavior and ABI slot;
former dispatch aliases continue to alias the dispatcher. The stubs declare
all metadata overloads. When two ABI overloads collapse to the same typed
Python signature, the later declaration gets a targeted
`overload-cannot-match` ignore so strict mypy accepts the metadata-exact count.

Regression coverage checks the representative SDK classes, every old public
name in those classes, collision fallbacks, exact compatibility-alias
behavior, strict mypy, and live Python calls for StorageFile,
RandomAccessStream, DecimalFormatter, XmlDocument, and Calendar.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Emit one stub declaration when multiple ABI candidates render to the same
full Python signature. This avoids duplicate IDE hover entries and needs no
mypy suppression for common Int64/UInt64-style projection collisions.

If parameter signatures are identical but return types differ, keep both
declarations and attach the targeted overload-cannot-match ignore to the later
one. Constructor overloads continue to deduplicate identical signatures.

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.66% 82.19% 86.38% regions
Python aggregate 70.95% n/a 36.85% branches
Python runtime 99.12% n/a 97.37% branches
Generated Python WinRT projections 70.29% n/a 30.41% branches
Generated Python WinRT implementations 72.09% n/a 46.52% 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

leileizhang (lei9444) and others added 16 commits September 24, 2026 15:53
Do not treat identically shaped methods on different interfaces as the same
native overload when checking compatibility. When a documented CLR group
occupies an old public name with an identically shaped method from another
interface, emit a compatibility dispatcher that tries the exact previously
selected interface method first. It reuses the implementation defined by the
canonical CLR-name group, so each native method body is generated once, while
the newly documented interface method remains projected under its own name.

Keep one stub declaration per distinct full Python signature. Only identical
parameter signatures with different return types retain a second declaration
and targeted mypy suppression.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Determine compatibility from the actual reachable dispatch representative, not
raw group membership, so a same-group newcomer cannot sort ahead of the exact
interface method selected before CLR-name grouping.

When an implementation is shared with a compatibility dispatcher, assign it a
stable private attribute even if its canonical group has one candidate. The
canonical public name aliases that private implementation, preventing a Python
subclass override from changing the compatibility dispatcher's native target.

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

# Conflicts:
#	.github/workflows/build.yml
#	tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs
#	tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lei9444
leileizhang (lei9444) merged commit f0814b8 into main Sep 29, 2026
33 checks passed
@lei9444
leileizhang (lei9444) deleted the lei9444-fix-python-overload-naming-and-dispatch branch September 29, 2026 03:56
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.

2 participants