Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/status/PYTHON_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ of a dynamic projection.
- [x] Add stage/swap generation and remove stale Python output.
- [x] Emit a consumable Python package manifest with an exact runtime
dependency.
- [x] Emit one canonical module and Python class per WinRT struct instead of
duplicating incompatible struct classes in every consumer module.
- [x] Make root and namespace exports lazy, keep ABI helpers in type-specific
facades, and expose only Python types from public package indexes.

## P0: runtime and generated API agreement

Expand All @@ -62,6 +66,8 @@ of a dynamic projection.
integers.
- [x] Make Python stubs part of E2E and run a static type checker.
- [x] Make `.pyi` generation the default for `--lang py`.
- [x] Pass full-package `mypy --strict`, including collection overrides and
cross-module struct arguments.

## P0: async semantics

Expand Down
19 changes: 19 additions & 0 deletions tests/e2e/e2e_specs.json
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,25 @@
}
]
},
{
"id": "struct_size_roundtrip",
"namespace": "Windows.Foundation",
"class": "PropertyValue",
"langs": ["py", "ts"],
"instantiate": { "kind": "none" },
"checks": [
{
"kind": "struct_roundtrip",
"member": "create_size",
"struct_module": "property_value",
"struct_class": "Size",
"struct_args": { "width": 640.0, "height": 480.0 },
"pack_fn": "pack_size",
"unpack_fn": "unpack_size",
"expected_fields": { "width": 640.0, "height": 480.0 }
}
]
},
{
"id": "array_i32_roundtrip",
"namespace": "Windows.Foundation",
Expand Down
250 changes: 157 additions & 93 deletions tests/e2e/runners/py_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def to_snake_case(name: str) -> str:
"""Convert PascalCase/camelCase to snake_case."""
value = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name)
value = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', value).lstrip('_').lower()
value = re.sub(r'_+', '_', value)
return collapse_winrt_uint_tokens(value)


Expand Down Expand Up @@ -476,13 +477,32 @@ async def run_check(
cr['pass'] = True

elif kind == 'struct_roundtrip':
struct_module = check.get(
'py_struct_module',
to_snake_case(check['struct_class']),
)
struct_mod = importlib.import_module(
f"{namespace_module_name(pkg_name, namespace)}.{check['struct_module']}"
f"{namespace_module_name(pkg_name, namespace)}.{struct_module}"
)
struct_cls = getattr(struct_mod, check['struct_class'])
pack = getattr(struct_mod, check['pack_fn'])
unpack = getattr(struct_mod, check['unpack_fn'])

# Create struct instance with kwargs
struct_obj = struct_cls(**{to_snake_case(k): v for k, v in check['struct_args'].items()})
roundtrip = unpack(pack(struct_obj).to_value())
if roundtrip != struct_obj:
cr['error'] = (
f'struct helper roundtrip returned {roundtrip!r}, '
f'expected {struct_obj!r}'
)
return cr
if roundtrip.__eq__(object()) is not NotImplemented:
cr['error'] = 'struct equality accepted a different type'
return cr
if check['struct_class'] not in repr(roundtrip):
cr['error'] = 'struct repr omitted the projected type name'
return cr

# Pass struct directly to static method (generated code handles pack internally)
static_method = getattr(cls, to_snake_case(check['member']))
Expand Down Expand Up @@ -1615,7 +1635,8 @@ def block_on_sta():
import dynwinrt as dw

# This spec runs last. Earlier E2E specs validate real generated
# call sites; this matrix covers each emitted copy of shared helpers.
# call sites; this matrix covers the shared runtime once and each
# module-local helper shape that remains after runtime extraction.
property_type = generated_type(pkg_name, 'PropertyType')
property_type_module = implementation_module_name(
pkg_name, 'Windows.Foundation', 'PropertyType'
Expand All @@ -1627,112 +1648,154 @@ def block_on_sta():
uri_module = implementation_module_name(
pkg_name, 'Windows.Foundation', 'Uri'
)
generated_package = importlib.import_module(pkg_name)
delegate_name = (
'TypedEventHandler_IMemoryBufferReference_Object'
)
delegate_module = importlib.import_module(
implementation_module_name(
pkg_name,
'Windows.Foundation',
delegate_name,
)
)
delegate_iid = getattr(
generated_package,
'IID_TypedEventHandler_IMemoryBufferReference_Object',
delegate_module,
f'IID_{delegate_name}',
)
delegate_params = getattr(
generated_package,
'TypedEventHandler_IMemoryBufferReference_Object_PARAM_TYPES',
delegate_module,
f'{delegate_name}_PARAM_TYPES',
)
reference_type = generated_type(pkg_name, 'IReference_UInt32')
value_type = dw.DynWinRTType.u32_type()
runtime_module = importlib.import_module(f'{pkg_name}._runtime')
module_paths = [
path
for path in sorted(Path(generated_dir).glob('*.py'))
if path.name not in ('__init__.py', '_runtime.py')
]
reference_modules = []
shared_definitions = (
'def _dynwinrt_enum(',
'def _dynwinrt_delegate(',
'def _dynwinrt_wrap_values(',
)
for module_path in module_paths:
source = module_path.read_text(encoding='utf-8')
if any(definition in source for definition in shared_definitions):
cr['error'] = (
f'{module_path.name}: shared runtime helper was duplicated'
)
return cr
if 'def _dynwinrt_box_reference(' in source:
reference_modules.append(module_path)

counters = {
'modules': 0,
'modules': len(module_paths),
'enum': 0,
'delegate': 0,
'wrap_values': 0,
'ireference': 0,
'struct_helpers': 0,
}

for module_path in sorted(Path(generated_dir).glob('*.py')):
if module_path.name == '__init__.py':
continue
generated_module = importlib.import_module(
f'{pkg_name}.{module_path.stem}'
)
counters['modules'] += 1

enum_helper = getattr(generated_module, '_dynwinrt_enum', None)
if enum_helper is not None:
converted = enum_helper(
property_type_module.removeprefix(f'{pkg_name}.'),
'PropertyType',
int(valid_enum),
)
unknown = enum_helper(
property_type_module.removeprefix(f'{pkg_name}.'),
'PropertyType',
invalid_enum,
)
if not isinstance(converted, property_type):
cr['error'] = (
f'{module_path.name}: valid enum was not projected'
)
return cr
if type(unknown) is not int or unknown != invalid_enum:
cr['error'] = (
f'{module_path.name}: unknown enum was not preserved'
)
return cr
counters['enum'] += 1
enum_helper = runtime_module._dynwinrt_enum
converted = enum_helper(
property_type_module.removeprefix(f'{pkg_name}.'),
'PropertyType',
int(valid_enum),
)
unknown = enum_helper(
property_type_module.removeprefix(f'{pkg_name}.'),
'PropertyType',
invalid_enum,
)
if not isinstance(converted, property_type):
cr['error'] = 'shared runtime did not project a valid enum'
return cr
if type(unknown) is not int or unknown != invalid_enum:
cr['error'] = 'shared runtime did not preserve an unknown enum'
return cr
counters['enum'] = 1

delegate_helper = getattr(
generated_module, '_dynwinrt_delegate', None
)
if delegate_helper is not None:
raw = dw.DynWinRTValue.null_value()
if delegate_helper(raw, delegate_iid, delegate_params) is not raw:
cr['error'] = (
f'{module_path.name}: raw delegate was not preserved'
)
return cr
try:
delegate_helper(17, delegate_iid, delegate_params)
cr['error'] = (
f'{module_path.name}: invalid delegate was accepted'
)
return cr
except TypeError:
pass
callback_value = delegate_helper(
lambda *_args: None,
delegate_iid,
delegate_params,
)
if not isinstance(callback_value, dw.DynWinRTValue):
cr['error'] = (
f'{module_path.name}: callable delegate was not wrapped'
)
return cr
callback_value.release()
counters['delegate'] += 1
delegate_helper = runtime_module._dynwinrt_delegate
raw = dw.DynWinRTValue.null_value()
if delegate_helper(raw, delegate_iid, delegate_params) is not raw:
cr['error'] = 'shared runtime did not preserve a raw delegate'
return cr
try:
delegate_helper(17, delegate_iid, delegate_params)
cr['error'] = 'shared runtime accepted an invalid delegate'
return cr
except TypeError:
pass
callback_value = delegate_helper(
lambda *_args: None,
delegate_iid,
delegate_params,
)
if not isinstance(callback_value, dw.DynWinRTValue):
cr['error'] = 'shared runtime did not wrap a callable delegate'
return cr
callback_value.release()
counters['delegate'] = 1

wrap_values = getattr(
generated_module, '_dynwinrt_wrap_values', None
wrapped = runtime_module._dynwinrt_wrap_values(
uri_module.removeprefix(f'{pkg_name}.'),
'Uri',
[dw.DynWinRTValue.null_value(), uri._obj],
)
if wrapped[0] is not None or not isinstance(wrapped[1], uri_type):
cr['error'] = 'shared runtime value wrapping branches failed'
return cr
if wrapped[1] is not uri:
cr['error'] = 'shared runtime did not reuse wrapper identity'
return cr
counters['wrap_values'] = 1

for namespace, struct_name, values in (
(
'Windows.Data.Text',
'TextSegment',
{'start_position': 3, 'length': 5},
),
(
'Windows.Foundation',
'EventRegistrationToken',
{'value': 9},
),
):
struct_module = importlib.import_module(
f'{namespace_module_name(pkg_name, namespace)}.'
f'{to_snake_case(struct_name)}'
)
if wrap_values is not None:
wrapped = wrap_values(
uri_module.removeprefix(f'{pkg_name}.'),
'Uri',
[dw.DynWinRTValue.null_value(), uri._obj],
struct_type = getattr(struct_module, struct_name)
pack = getattr(
struct_module,
f'pack_{to_snake_case(struct_name)}',
)
unpack = getattr(
struct_module,
f'unpack_{to_snake_case(struct_name)}',
)
value = struct_type(**values)
roundtrip = unpack(pack(value).to_value())
if roundtrip != value or struct_name not in repr(roundtrip):
cr['error'] = (
f'{struct_name}: canonical struct helper roundtrip failed'
)
if (
wrapped[0] is not None
or not isinstance(wrapped[1], uri_type)
):
cr['error'] = (
f'{module_path.name}: value wrapping branches failed'
)
return cr
if wrapped[1] is not uri:
cr['error'] = (
f'{module_path.name}: wrapper identity was not reused'
)
return cr
counters['wrap_values'] += 1
return cr
if roundtrip.__eq__(object()) is not NotImplemented:
cr['error'] = (
f'{struct_name}: struct equality accepted another type'
)
return cr
counters['struct_helpers'] += 1

for module_path in reference_modules:
generated_module = importlib.import_module(
f'{pkg_name}.{module_path.stem}'
)
box_reference = getattr(
generated_module, '_dynwinrt_box_reference', None
)
Expand Down Expand Up @@ -1783,10 +1846,11 @@ def block_on_sta():
uri._obj.release()
if (
counters['modules'] < 100
or counters['enum'] < 50
or counters['delegate'] < 50
or counters['wrap_values'] < 50
or counters['enum'] != 1
or counters['delegate'] != 1
or counters['wrap_values'] != 1
or counters['ireference'] < 1
or counters['struct_helpers'] != 2
):
cr['error'] = f'generated helper matrix was too small: {counters}'
else:
Expand Down
6 changes: 3 additions & 3 deletions tools/dynwinrt-codegen/src/codegen/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,11 +647,11 @@ mod tests {
};
assert_eq!(
py_convert_return("r", Some(&rt), false, &known),
"(lambda value: None if value.is_null() else _dynwinrt_symbol('uri', 'Uri')._from_native(value))(r)"
"(lambda value: None if value.is_null() else _dynwinrt_symbol('windows__foundation__uri', 'Uri')._from_native(value))(r)"
);
assert_eq!(
py_convert_array_return("r", &rt, &known),
"_dynwinrt_wrap_values('uri', 'Uri', r.to_values())"
"_dynwinrt_wrap_values('windows__foundation__uri', 'Uri', r.to_values())"
);
}

Expand All @@ -674,7 +674,7 @@ mod tests {
let known = HashSet::from(["DayOfWeek".to_string()]);
assert_eq!(
py_convert_return("r", Some(&en), false, &known),
"_dynwinrt_enum('day_of_week', 'DayOfWeek', r.to_number())"
"_dynwinrt_enum('windows__globalization__day_of_week', 'DayOfWeek', r.to_number())"
);
}

Expand Down
Loading
Loading