Fix Python overload naming and interface-parameter dispatch - #193
Open
leileizhang (lei9444) wants to merge 7 commits into
Open
leileizhang (lei9444) wants to merge 7 commits into
leileizhang (lei9444) wants to merge 7 commits into
Conversation
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>
Mixed-language test coverageWorkflow status: ✅ Passed
|
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
[Overload]ABI names plus suffix guesses, so the documented CLR names were often missing or split. In Windows.winmd, 566 of 623 overload groups were not fully under the documented name, and 39 lacked it entirely (e.g.StorageFile.copy_async,ToastNotifier.update,DecimalFormatter.format).isinstance. Runtime-class objects never pass that check, so calls likeDataWriter(InMemoryRandomAccessStream())failed with "No matching constructor".Key changes
.pyand the.pyigenerators use that plan. This refactor on its own leaves the generated output byte-identical.*Handlers) names are unchanged.Notes
int, with the same return type) are emitted once. If the parameters match but the return types differ, both overloads are kept, and the later one gets a targeted# type: ignore[overload-cannot-match].