Skip to content

Add explicit WinRT Object boxing and type-preserving unboxing to Python - #194

Open
leileizhang (lei9444) wants to merge 4 commits into
mainfrom
lei9444-explicit-python-object-boxing
Open

leileizhang (lei9444) wants to merge 4 commits into
mainfrom
lei9444-explicit-python-object-boxing

Conversation

@lei9444

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

Copy link
Copy Markdown
Contributor

Problem

unbox_object() lacked several IPropertyValue types, lost numeric type information, and Python had no explicit boxing helper.
Automatic conversion at every Object position is unsafe because Object may be a runtime object and Python values do not identify one exact WinRT type.

Key changes

  • Keep generated Object positions raw; add explicit to_winrt_object(value, property_type=None).
  • Extend unbox_object(value, *, preserve_type=False) to DateTime, TimeSpan, Point/Size/Rect, their arrays, and InspectableArray.
  • Add dynwinrt.values: exact numeric tags, typed arrays, geometry values, and a PropertyType enum.
  • Model and box all 37 payload-bearing PropertyTypes in the Rust core.
  • Preserve JavaScript behavior with a compile-only adapter.
  • Cover all 37 types plus real StorageFile and DeviceInformation properties.

Notes

  • Plain int means Int32 only; enum members, empty/mixed lists, and InspectableArray require an explicit type.
  • Existing WinRT objects pass through by identity; re-boxed values preserve type and value, not the original box identity.
  • Released values, wrappers, and nested array elements raise the standard lifecycle error and are never converted to null.
  • Legitimate WinRT null values retain their existing behavior.
  • Generated code and Object semantics remain unchanged.

Generated Object positions stay native (IInspectable); applications now
convert boxed values explicitly at the boundaries that want value semantics.

Core: PropertyValueData models every PropertyType that has a payload (37 of
41), unbox_property_value reports payload-less boxes as Unsupported, and
box_property_value creates system PropertyValue boxes. JavaScript keeps its
exact behavior and messages through a compile-only adapter.

Python:
- unbox_object additionally supports DateTime, TimeSpan, Point, Size, Rect,
  their arrays and InspectableArray (elements unboxed by the same rules);
  already-supported types return the same values, and payload-less boxes
  still raise OSError.
- unbox_object(raw, preserve_type=True) returns dynwinrt.values tags and typed
  arrays, so to_winrt_object restores the exact PropertyType and value.
- to_winrt_object(value, property_type=None) boxes only unambiguous values:
  a plain int is Int32 only, lists must be homogeneous, and enum members,
  empty or mixed lists and InspectableArray need an explicit type.
- The tags, typed arrays, Point/Size/Rect and PropertyType live in the new
  dynwinrt.values submodule, outside the top-level namespace.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Python-only checks on generated bindings:
- object_value_roundtrip: every PropertyValue.create_* factory (37
  PropertyTypes) round-trips through unbox_object(preserve_type=True) and
  to_winrt_object with its exact PropertyType, and dynwinrt.values.PropertyType
  matches the metadata enum.
- object_value_storage_properties: a temporary file's System.Size unboxes as
  UInt64 in preserve mode and System.DateModified as an aware datetime.
- object_value_device_properties: DeviceInformation.properties values unbox
  as str, bool and UUID, skipping gracefully without devices.

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% 81.96% 86.33% regions
Python aggregate 71.67% n/a 38.62% branches
Python runtime 98.16% n/a 94.44% branches
Generated Python WinRT projections 69.44% n/a 27.09% branches
Generated Python WinRT implementations 72.35% n/a 47.05% 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

Bring #188's lifecycle-aware Python runtime, implementation callback checks,
apartment constants, actionable errors, and released-reference E2E coverage
into the explicit Object value conversion branch.

Resolve the overlapping runtime API by keeping unbox_object in the explicit
conversion layer and routing every native read through DynWinRTValue's live
input check. Released top-level values, projected wrappers, inferred list
items, typed-array items, and explicit InspectableArray items now raise the
standard released-input RuntimeError rather than appearing as WinRT null.
Legitimate null values retain their existing boxing and unboxing behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
leileizhang (lei9444) added a commit that referenced this pull request Sep 28, 2026
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Add an opt-in value view for WinRT Object-valued maps in Python

dynwinrt.values.object_value_view(mapping, *, preserve_type=False) wraps a
generated IMap or IMapView wrapper whose values are Object, such as
PropertySet, ValueSet or DeviceInformation.properties. It returns a live
MutableObjectValueView or read-only ObjectValueView that holds no WinRT
reference of its own: reads unbox with unbox_object(), writes box with
to_winrt_object()'s default rules, and view.raw is the generated map.

QueryInterface for IMap/IMapView<String or Guid, Object> confirms the value
type, so other maps such as StringMap raise TypeError, and the overloads
reject them statically. A box without a Python form (an unsupported
PropertyType or a DateTime outside datetime's range) reads back raw so that
dict(view) does not fail on one odd entry. Generated code, unbox_object()
and to_winrt_object() are unchanged.

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

* Type MutableObjectValueView.update() like item assignment

update() writes through __setitem__ and to_winrt_object(), so its overloads
now accept any value, as item assignment already does. They mirror
MutableMapping.update(), which allows keyword arguments only for str keys.

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

* Type MutableObjectValueView.setdefault() like item assignment

setdefault() now accepts any default that to_winrt_object() accepts, like
__setitem__ and update(), and returns the read type: after storing a missing
key it reads the value back, so (1, 2) returns [1, 2] and a runtime object
its DynWinRTValue. MutableMapping.setdefault() would return the default
itself. A targeted type: ignore[override] covers typeshed's self-typed
overload, which infers "-> None" for a value type that includes None.

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

* Address Object map view review findings

Mark only binding-classified unsupported PropertyTypes so a supported
IPropertyValue getter that returns the same HRESULT still propagates. Verify
that a generated wrapper's actual map dispatch pointer is the reflexive
IMap/IMapView<String or Guid, Object> pointer, rejecting another map wrapper
on the same COM identity while preserving .raw. Constrain the public view key
type to str or UUID.

Add a synthetic dual-map COM object, getter-error identity checks, strict
mypy/Pyright key controls, and generated Guid-map E2E coverage.

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

* Validate Object map dispatch metadata

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

* Regress released Object map views with updated runtime

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