diff --git a/docs/status/PYTHON_CHECKLIST.md b/docs/status/PYTHON_CHECKLIST.md index c099a8c9..e2095731 100644 --- a/docs/status/PYTHON_CHECKLIST.md +++ b/docs/status/PYTHON_CHECKLIST.md @@ -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 @@ -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 diff --git a/tests/e2e/e2e_specs.json b/tests/e2e/e2e_specs.json index 69758ba9..c2963bf3 100644 --- a/tests/e2e/e2e_specs.json +++ b/tests/e2e/e2e_specs.json @@ -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", diff --git a/tests/e2e/runners/py_runner.py b/tests/e2e/runners/py_runner.py index e09e85ec..b6fc28c8 100644 --- a/tests/e2e/runners/py_runner.py +++ b/tests/e2e/runners/py_runner.py @@ -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) @@ -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'])) @@ -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' @@ -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 ) @@ -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: diff --git a/tools/dynwinrt-codegen/src/codegen/common.rs b/tools/dynwinrt-codegen/src/codegen/common.rs index 8f989d33..ca129286 100644 --- a/tools/dynwinrt-codegen/src/codegen/common.rs +++ b/tools/dynwinrt-codegen/src/codegen/common.rs @@ -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())" ); } @@ -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())" ); } diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs index 28f00bb1..a5fb2354 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs @@ -4,7 +4,7 @@ //! Python runtime class generation. use super::imports::{emit_type_checking_imports, format_py_type_import}; -use super::structs::generate_struct_helpers; +use super::structs::{generate_struct_helpers, generate_struct_imports}; use super::*; use crate::codegen::winrt::extensions::winui::{self, WinUiAbiType}; use crate::codegen::winrt::python::collections::{ @@ -85,6 +85,9 @@ pub fn generate_class( ) { out.push_str(ASYNC_IMPORT_LINE); } + if python_module_layout_installed() { + out.push_str(&generate_struct_imports(&used_structs)); + } if has_ireference_input( class .all_interfaces() @@ -112,12 +115,34 @@ pub fn generate_class( } // Collection generics import (skip delegates) + let mut imported_names: HashSet = HashSet::new(); let collection_names = collect_used_generics_from_class(class); for cname in &collection_names { if !delegate_names.contains(cname) { let module = to_snake_case_filename(cname); type_checking_imports .push(format!("from .{} import {} # noqa: F401\n", module, cname)); + imported_names.insert(cname.clone()); + } + } + for iface in class.all_interfaces() { + if iface.generic_piid.as_deref() + == Some(crate::codegen::winrt::python::collections::IOBSERVABLE_VECTOR_PIID) + { + if imported_names.insert(iface.name.clone()) { + type_checking_imports.push(format_py_type_import( + &iface.namespace, + &iface.name, + crate::types::TypeKind::Interface, + )); + } + let event_args = "IVectorChangedEventArgs"; + if imported_names.insert(event_args.into()) { + type_checking_imports.push(format!( + "from .{} import {event_args} # noqa: F401\n", + to_snake_case_filename(event_args) + )); + } } } @@ -132,7 +157,6 @@ pub fn generate_class( } // Type imports - let mut imported_names: HashSet = HashSet::new(); let imports = collect_type_imports(class); let mut sorted_imports: Vec<_> = imports.iter().collect(); sorted_imports @@ -240,9 +264,11 @@ pub fn generate_class( } // Struct helpers - for s in &used_structs { - out.push_str(&generate_struct_helpers(s)); - out.push('\n'); + if !python_module_layout_installed() { + for s in &used_structs { + out.push_str(&generate_struct_helpers(s)); + out.push('\n'); + } } // Class declaration @@ -1574,7 +1600,9 @@ mod tests { assert!(forward.contains( "isinstance(_bound[0], int) and not isinstance(_bound[0], bool) and not isinstance(_bound[0], __import__('enum').Enum)" )); - assert!(forward.contains("isinstance(_bound[0], _dynwinrt_symbol('mode', 'Mode'))")); + assert!( + forward.contains("isinstance(_bound[0], _dynwinrt_symbol('contoso__mode', 'Mode'))") + ); let forward_script = forward.replace("if cls is Widget:", "if cls is WidgetForward:"); let reverse_script = reverse.replace("if cls is Widget:", "if cls is WidgetReverse:"); diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/index.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/index.rs index 19de5ac3..241f1606 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/index.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/index.rs @@ -3,6 +3,7 @@ //! Python package index generation. +use super::super::native_types::foundation_type; use super::structs::py_struct_export_names; use super::*; @@ -19,25 +20,11 @@ pub fn generate_index( sorted_classes.sort_by(|a, b| a.name.cmp(&b.name)); for class in sorted_classes { if seen.insert(class.name.clone()) { - let struct_names: Vec<_> = collect_used_structs_from_class(class) - .iter() - .flat_map(|s| py_struct_export_names(s)) - .filter(|n| seen.insert(n.clone())) - .collect(); let module = python_module_name(&class.namespace, &class.name); - if struct_names.is_empty() { - out.push_str(&format!( - "from .{} import {} # noqa: F401\n", - module, class.name - )); - } else { - out.push_str(&format!( - "from .{} import {}, {} # noqa: F401\n", - module, - class.name, - struct_names.join(", ") - )); - } + out.push_str(&format!( + "from .{} import {} # noqa: F401\n", + module, class.name + )); } } let mut sorted_ifaces: Vec<_> = interfaces.iter().collect(); @@ -49,11 +36,6 @@ pub fn generate_index( let is_delegate = iface.methods.iter().any(|m| m.name == ".ctor") && iface.methods.iter().any(|m| m.name == "Invoke"); let module = python_module_name(&iface.namespace, &iface.name); - let struct_names: Vec<_> = collect_used_structs_from_iface(iface) - .iter() - .flat_map(|s| py_struct_export_names(s)) - .filter(|n| seen.insert(n.clone())) - .collect(); if is_delegate { out.push_str(&format!( "from .{module} import IID_{iname}, {iname}_PARAM_TYPES # noqa: F401\n", @@ -61,20 +43,11 @@ pub fn generate_index( iname = iface.name )); } else { - if struct_names.is_empty() { - out.push_str(&format!( - "from .{module} import IID_{iname}, {iname} # noqa: F401\n", - module = module, - iname = iface.name - )); - } else { - out.push_str(&format!( - "from .{module} import IID_{iname}, {iname}, {structs} # noqa: F401\n", - module = module, - iname = iface.name, - structs = struct_names.join(", ") - )); - } + out.push_str(&format!( + "from .{module} import IID_{iname}, {iname} # noqa: F401\n", + module = module, + iname = iface.name + )); } } let mut sorted_enums: Vec<_> = enums.iter().collect(); @@ -103,6 +76,127 @@ pub fn generate_index( out } +pub fn generate_public_index( + classes: &[ClassMeta], + interfaces: &[InterfaceMeta], + enums: &[TypeMeta], +) -> String { + let mut out = String::from(HEADER); + let mut seen = HashSet::new(); + + let mut classes = classes.iter().collect::>(); + classes.sort_by(|left, right| left.name.cmp(&right.name)); + for class in classes { + if seen.insert(class.name.clone()) { + out.push_str(&format!( + "from .{} import {} # noqa: F401\n", + python_public_qualified_module_name(&class.namespace, &class.name), + class.name + )); + } + } + + let mut interfaces = interfaces.iter().collect::>(); + interfaces.sort_by(|left, right| left.name.cmp(&right.name)); + for interface in interfaces { + let is_delegate = interface + .methods + .iter() + .any(|method| method.name == ".ctor") + && interface + .methods + .iter() + .any(|method| method.name == "Invoke"); + if !is_delegate && seen.insert(interface.name.clone()) { + out.push_str(&format!( + "from .{} import {} # noqa: F401\n", + python_public_qualified_module_name(&interface.namespace, &interface.name), + interface.name + )); + } + } + + let mut enums = enums.iter().collect::>(); + enums.sort_by_key(|typ| match typ { + TypeMeta::Enum { name, .. } => name.as_str(), + _ => "", + }); + for typ in enums { + let TypeMeta::Enum { + namespace, name, .. + } = typ + else { + continue; + }; + if seen.insert(name.clone()) { + out.push_str(&format!( + "from .{} import {} # noqa: F401\n", + python_public_qualified_module_name(namespace, name), + name + )); + } + } + out +} + +pub fn generate_struct_index(structs: &[TypeMeta]) -> String { + let mut out = String::from(HEADER); + let mut seen = HashSet::new(); + let mut sorted = structs.iter().collect::>(); + sorted.sort_by_key(|typ| match typ { + TypeMeta::Struct { + namespace, name, .. + } => format!("{namespace}.{name}"), + _ => String::new(), + }); + for typ in sorted { + let TypeMeta::Struct { + namespace, name, .. + } = typ + else { + continue; + }; + if !seen.insert((namespace, name)) { + continue; + } + out.push_str(&format!( + "from .{} import {} # noqa: F401\n", + python_module_name(namespace, name), + py_struct_export_names(typ).join(", ") + )); + } + out +} + +pub fn generate_public_struct_index(structs: &[TypeMeta]) -> String { + let mut out = String::from(HEADER); + let mut seen = HashSet::new(); + let mut sorted = structs.iter().collect::>(); + sorted.sort_by_key(|typ| match typ { + TypeMeta::Struct { + namespace, name, .. + } => format!("{namespace}.{name}"), + _ => String::new(), + }); + for typ in sorted { + let TypeMeta::Struct { + namespace, name, .. + } = typ + else { + continue; + }; + if foundation_type(typ).is_some() || !seen.insert((namespace, name)) { + continue; + } + out.push_str(&format!( + "from .{} import {} # noqa: F401\n", + python_public_qualified_module_name(namespace, name), + name + )); + } + out +} + /// Append new types to an existing `__init__.py`. pub fn append_to_index( existing: &str, @@ -150,24 +244,10 @@ pub fn append_to_index( for class in sorted_classes { let module = python_module_name(&class.namespace, &class.name); if !exported_modules.contains(&module) && seen.insert(class.name.clone()) { - let struct_names: Vec<_> = collect_used_structs_from_class(class) - .iter() - .flat_map(|s| py_struct_export_names(s)) - .filter(|n| seen.insert(n.clone())) - .collect(); - if struct_names.is_empty() { - out.push_str(&format!( - "from .{} import {} # noqa: F401\n", - module, class.name - )); - } else { - out.push_str(&format!( - "from .{} import {}, {} # noqa: F401\n", - module, - class.name, - struct_names.join(", ") - )); - } + out.push_str(&format!( + "from .{} import {} # noqa: F401\n", + module, class.name + )); } } @@ -180,11 +260,6 @@ pub fn append_to_index( } let is_delegate = iface.methods.iter().any(|m| m.name == ".ctor") && iface.methods.iter().any(|m| m.name == "Invoke"); - let struct_names: Vec<_> = collect_used_structs_from_iface(iface) - .iter() - .flat_map(|s| py_struct_export_names(s)) - .filter(|n| seen.insert(n.clone())) - .collect(); if is_delegate { out.push_str(&format!( "from .{module} import IID_{iname}, {iname}_PARAM_TYPES # noqa: F401\n", @@ -192,20 +267,11 @@ pub fn append_to_index( iname = iface.name )); } else { - if struct_names.is_empty() { - out.push_str(&format!( - "from .{module} import IID_{iname}, {iname} # noqa: F401\n", - module = module, - iname = iface.name - )); - } else { - out.push_str(&format!( - "from .{module} import IID_{iname}, {iname}, {structs} # noqa: F401\n", - module = module, - iname = iface.name, - structs = struct_names.join(", ") - )); - } + out.push_str(&format!( + "from .{module} import IID_{iname}, {iname} # noqa: F401\n", + module = module, + iname = iface.name + )); } } diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs index dacd1099..7760a4d7 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs @@ -13,18 +13,23 @@ use crate::meta::{ClassMeta, InterfaceMeta, MethodMeta, ParamDirection}; use crate::types::{TypeKind, TypeMeta}; use crate::codegen::winrt::shared::imports::{ - collect_iface_type_imports, collect_type_imports, collect_used_generics_from_class, - collect_used_generics_from_methods, ireference_inner_type, + collect_iface_type_imports, collect_struct_field_type_imports, collect_type_imports, + collect_used_generics_from_class, collect_used_generics_from_methods, + collect_used_generics_from_type, ireference_inner_type, }; use crate::codegen::winrt::shared::structs::{ collect_used_structs_from_class, collect_used_structs_from_iface, + collect_used_structs_from_struct, }; use super::method::{ InstanceOverload, StaticOverload, StaticOverloadKind, generate_iface_instance_method, generate_instance_method_group, generate_static_method_group, py_method_type_guard, }; -use super::naming::{is_py_reserved, python_module_name, to_snake_case, to_snake_case_filename}; +use super::naming::{ + is_py_reserved, python_module_layout_installed, python_module_name, + python_public_qualified_module_name, to_snake_case, to_snake_case_filename, +}; use super::shared::reorder_getters_before_setters; use super::signature::{ py_collect_runtime_class_iid_consts, py_dynwinrt_type, py_generate_interface_registration, @@ -38,6 +43,23 @@ use super::type_helpers::methods_have_async_output; const HEADER: &str = "# Generated by dynwinrt-codegen — do not edit\n"; const FUTURE_ANNOTATIONS: &str = "from __future__ import annotations\n"; const IMPORT_LINE: &str = "\ +from ._runtime import ( + Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, + TYPE_CHECKING, UUID, WinGUID, datetime, timedelta, + DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, + DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, + _property, _weakref_ref, + _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_create_delegate, + _dynwinrt_datetime_to_ticks, _dynwinrt_delegate, _dynwinrt_enum, _dynwinrt_guid, + _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, + _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, + _dynwinrt_cache_projected, _dynwinrt_projected_from_native, + _dynwinrt_symbol, _dynwinrt_track_projected, _dynwinrt_uuid, + _dynwinrt_vector, _dynwinrt_wrap_values, +) +"; + +const RUNTIME_SUPPORT_BODY: &str = "\ from builtins import property as _property from contextvars import copy_context as _copy_context from functools import lru_cache @@ -95,6 +117,10 @@ def _dynwinrt_delegate(value, iid, parameter_types): return _dynwinrt_create_delegate(iid, parameter_types, value).to_value() \n"; +pub fn generate_runtime_support_module() -> String { + format!("{HEADER}{FUTURE_ANNOTATIONS}{RUNTIME_SUPPORT_BODY}") +} + const ASYNC_IMPORT_LINE: &str = "\ from dynwinrt import WinRTAsync, WinRTAsyncWithProgress from dynwinrt.dynwinrt import _DynWinRTAsync, _DynWinRTAsyncWithProgress @@ -148,5 +174,9 @@ fn has_ireference_struct_field(structs: &[TypeMeta]) -> bool { } pub use class::generate_class; -pub use index::{append_to_index, generate_index}; +pub use index::{ + append_to_index, generate_index, generate_public_index, generate_public_struct_index, + generate_struct_index, +}; +pub use structs::generate_struct; pub use types::{generate_enum, generate_interface}; diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/structs.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/structs.rs index aefa945d..c35f6097 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/structs.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/structs.rs @@ -3,6 +3,7 @@ //! Python struct projection helpers. +use super::imports::{emit_type_checking_imports, format_py_type_import}; use super::*; use crate::codegen::winrt::python::native_types::{FoundationType, foundation_type}; use crate::codegen::winrt::python::type_helpers::py_optional_type; @@ -12,6 +13,78 @@ use crate::types::FieldMeta; // Struct helpers: Python dataclass-style + _unpack/_pack functions // ====================================================================== +fn struct_runtime_import_names(s: &TypeMeta) -> Vec { + let TypeMeta::Struct { name, .. } = s else { + return Vec::new(); + }; + let snake = to_snake_case(name); + let mut names = py_struct_export_names(s); + names.extend([ + format!("_{name}_TYPE"), + format!("_pack_{snake}"), + format!("_unpack_{snake}"), + ]); + names +} + +pub(super) fn generate_struct_imports(structs: &[TypeMeta]) -> String { + let mut imports = structs + .iter() + .filter_map(|typ| { + let TypeMeta::Struct { + namespace, name, .. + } = typ + else { + return None; + }; + Some(format!( + "from .{} import {} # noqa: F401\n", + python_module_name(namespace, name), + struct_runtime_import_names(typ).join(", ") + )) + }) + .collect::>(); + imports.sort(); + imports.dedup(); + imports.concat() +} + +pub fn generate_struct(s: &TypeMeta) -> Option { + let TypeMeta::Struct { name, .. } = s else { + return None; + }; + if name == "HResult" { + return None; + } + + let mut out = String::new(); + out.push_str(HEADER); + out.push_str(FUTURE_ANNOTATIONS); + out.push_str(IMPORT_LINE); + + let dependencies = collect_used_structs_from_struct(s); + out.push_str(&generate_struct_imports(&dependencies)); + if has_ireference_struct_field(std::slice::from_ref(s)) { + out.push_str(IREFERENCE_HELPER); + } + out.push('\n'); + + let mut type_checking_imports = collect_struct_field_type_imports(s) + .into_iter() + .map(|type_ref| format_py_type_import(&type_ref.namespace, &type_ref.name, type_ref.kind)) + .collect::>(); + type_checking_imports.extend(collect_used_generics_from_type(s).into_iter().map(|name| { + format!( + "from .{} import {} # noqa: F401\n", + to_snake_case_filename(&name), + name + ) + })); + emit_type_checking_imports(&mut out, type_checking_imports); + out.push_str(&generate_struct_helpers(s)); + Some(out) +} + pub(super) fn generate_struct_helpers(s: &TypeMeta) -> String { if let Some(kind) = foundation_type(s) { return generate_foundation_struct_helpers(s, kind); diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/types.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/types.rs index 2220d4d7..865a819b 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/types.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/types.rs @@ -4,7 +4,7 @@ //! Python enum, interface, and delegate generation. use super::imports::{emit_type_checking_imports, format_py_type_import}; -use super::structs::generate_struct_helpers; +use super::structs::{generate_struct_helpers, generate_struct_imports}; use super::*; use crate::codegen::winrt::python::collections::{ CollectionKind, interface_kind, map_iterable_name, observable_vector_name, runtime_mixin, @@ -106,6 +106,9 @@ pub fn generate_interface( if methods_have_async_output(iface.methods.iter()) { out.push_str(ASYNC_IMPORT_LINE); } + if python_module_layout_installed() { + out.push_str(&generate_struct_imports(&used_structs)); + } if has_ireference_input(iface.methods.iter()) || has_ireference_struct_field(&used_structs) { out.push_str(IREFERENCE_HELPER); } @@ -126,21 +129,21 @@ pub fn generate_interface( type_checking_imports .push(format!("from .{} import {} # noqa: F401\n", module, cname)); } - if let Some(vector_name) = &observable_vector - && !collection_names.contains(vector_name) - { - let module = to_snake_case_filename(vector_name); - type_checking_imports.push(format!( - "from .{module} import {vector_name} # noqa: F401\n" - )); - } - if observable_vector.is_some() { - let event_args = "IVectorChangedEventArgs"; - let module = to_snake_case_filename(event_args); - type_checking_imports.push(format!( - "from .{module} import {event_args} # noqa: F401\n" - )); - } + } + if let Some(vector_name) = &observable_vector + && !collection_names.contains(vector_name) + { + let module = to_snake_case_filename(vector_name); + type_checking_imports.push(format!( + "from .{module} import {vector_name} # noqa: F401\n" + )); + } + if observable_vector.is_some() { + let event_args = "IVectorChangedEventArgs"; + let module = to_snake_case_filename(event_args); + type_checking_imports.push(format!( + "from .{module} import {event_args} # noqa: F401\n" + )); } // Import delegate IID + PARAM_TYPES @@ -192,9 +195,11 @@ pub fn generate_interface( out.push('\n'); // Struct helpers - for s in &used_structs { - out.push_str(&generate_struct_helpers(s)); - out.push('\n'); + if !python_module_layout_installed() { + for s in &used_structs { + out.push_str(&generate_struct_helpers(s)); + out.push('\n'); + } } // Wrapper class diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/method.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/method.rs index 96be151c..bcffa8c3 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/method.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/method.rs @@ -519,6 +519,8 @@ pub(crate) fn generate_instance_method_group( } out.push_str(&format!(" def {public_name}(self, *args, **kwargs):\n")); + let public_params = get_in_params(ordered_overloads[0].method); + out.push_str(&method_pydoc(ordered_overloads[0].method, &public_params)); for (overload, private_name) in ordered_overloads.iter().zip(private_names) { let in_params = get_in_params(overload.method); let parameter_names = in_params @@ -638,6 +640,8 @@ pub(crate) fn generate_static_method_group( out.push_str(" @staticmethod\n"); out.push_str(&format!(" def {public_name}(*args, **kwargs):\n")); + let public_params = get_in_params(ordered_overloads[0].method); + out.push_str(&method_pydoc(ordered_overloads[0].method, &public_params)); for (overload, private_name) in ordered_overloads.iter().zip(private_names) { let in_params = get_in_params(overload.method); let parameter_names = in_params diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/mod.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/mod.rs index 5f0a5018..7d39e3d2 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/mod.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/mod.rs @@ -17,8 +17,9 @@ pub(crate) mod type_helpers; pub use generator::*; pub use naming::{ - PythonTypeIdentity, install_python_module_layout, python_module_name, - python_namespace_segments, python_public_module_name, to_snake_case_filename, + PythonModuleLayoutGuard, PythonTypeIdentity, install_python_module_layout, + python_module_layout_installed, python_module_name, python_namespace_segments, + python_public_module_name, python_public_qualified_module_name, to_snake_case_filename, }; pub(crate) fn collect_referenced_delegate_names( @@ -118,17 +119,17 @@ pub(crate) fn collect_runtime_delegate_names( result } -pub fn package_struct_identities( +pub fn package_structs( classes: &[crate::meta::ClassMeta], interfaces: &[crate::meta::InterfaceMeta], -) -> Vec<(String, String)> { +) -> Vec { use crate::codegen::winrt::shared::structs::{ collect_used_structs_from_class, collect_used_structs_from_iface, }; use crate::types::TypeMeta; - use std::collections::HashSet; + use std::collections::BTreeMap; - let mut identities = HashSet::new(); + let mut structs = BTreeMap::new(); for typ in classes .iter() .flat_map(collect_used_structs_from_class) @@ -136,10 +137,85 @@ pub fn package_struct_identities( { if let TypeMeta::Struct { namespace, name, .. - } = typ + } = &typ { - identities.insert((namespace, name)); + structs + .entry((namespace.clone(), name.clone())) + .or_insert(typ); + } + } + structs.into_values().collect() +} + +pub fn package_struct_identities( + classes: &[crate::meta::ClassMeta], + interfaces: &[crate::meta::InterfaceMeta], +) -> Vec<(String, String)> { + package_structs(classes, interfaces) + .into_iter() + .filter_map(|typ| match typ { + crate::types::TypeMeta::Struct { + namespace, name, .. + } => Some((namespace, name)), + _ => None, + }) + .collect() +} + +pub fn validate_struct_symbol_uniqueness( + classes: &[crate::meta::ClassMeta], + interfaces: &[crate::meta::InterfaceMeta], +) -> Result<(), String> { + use crate::codegen::winrt::shared::structs::{ + collect_used_structs_from_class, collect_used_structs_from_iface, + collect_used_structs_from_struct, + }; + use crate::types::TypeMeta; + use std::collections::BTreeMap; + + fn validate(owner: &str, structs: impl IntoIterator) -> Result<(), String> { + let mut identities = BTreeMap::::new(); + for typ in structs { + let TypeMeta::Struct { + namespace, name, .. + } = typ + else { + continue; + }; + let full_name = format!("{namespace}.{name}"); + if let Some(existing) = identities.insert(name.clone(), full_name.clone()) + && existing != full_name + { + return Err(format!( + "Python generation cannot safely emit `{owner}` because `{existing}` and \ + `{full_name}` both require the struct symbols `{name}`, `_{name}_TYPE`, \ + `_pack_{snake}`, and `_unpack_{snake}`", + snake = to_snake_case_filename(&name), + )); + } } + Ok(()) + } + + for class in classes { + validate(&class.full_name, collect_used_structs_from_class(class))?; + } + for interface in interfaces { + validate( + &format!("{}.{}", interface.namespace, interface.name), + collect_used_structs_from_iface(interface), + )?; + } + for typ in package_structs(classes, interfaces) { + let TypeMeta::Struct { + namespace, name, .. + } = &typ + else { + continue; + }; + let mut dependencies = vec![typ.clone()]; + dependencies.extend(collect_used_structs_from_struct(&typ)); + validate(&format!("{namespace}.{name}"), dependencies)?; } - identities.into_iter().collect() + Ok(()) } diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/naming.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/naming.rs index a90b579d..fd9c017c 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/naming.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/naming.rs @@ -32,6 +32,10 @@ impl Drop for PythonModuleLayoutGuard { } } +pub fn python_module_layout_installed() -> bool { + MODULE_LAYOUT.with(|layout| layout.borrow().is_some()) +} + pub fn install_python_module_layout( identities: impl IntoIterator, ) -> Result { @@ -91,11 +95,26 @@ pub fn python_module_name(namespace: &str, name: &str) -> String { name: name.to_string(), }) .cloned() + .or_else(|| layout.unique_modules.get(name).cloned()) }) - .unwrap_or_else(|| to_snake_case(name)) + .unwrap_or_else(|| qualified_module_name(namespace, name)) }) } +fn qualified_module_name(namespace: &str, name: &str) -> String { + let namespace = namespace + .split('.') + .filter(|segment| !segment.is_empty()) + .map(to_snake_case) + .collect::>() + .join("__"); + if namespace.is_empty() { + to_snake_case(name) + } else { + format!("{namespace}__{}", to_snake_case(name)) + } +} + pub fn python_namespace_segments(namespace: &str) -> Vec { namespace .split('.') @@ -108,6 +127,12 @@ pub fn python_public_module_name(name: &str) -> String { to_snake_case(name) } +pub fn python_public_qualified_module_name(namespace: &str, name: &str) -> String { + let mut segments = python_namespace_segments(namespace); + segments.push(python_public_module_name(name)); + segments.join(".") +} + fn is_winrt_uint_suffix(token: &str) -> bool { matches!(token, "int8" | "int16" | "int32" | "int64") } @@ -232,6 +257,14 @@ mod tests { ); } + #[test] + fn public_qualified_module_uses_namespace_facades() { + assert_eq!( + python_public_qualified_module_name("Microsoft.UI.Xaml.Controls", "Button"), + "microsoft.ui.xaml.controls.button" + ); + } + #[test] fn snake_case_only_collapses_uint_word_boundaries() { assert_eq!(to_snake_case("MenuInt8"), "menu_int8"); @@ -265,4 +298,30 @@ mod tests { assert!(err.contains("Example.Uint32"), "{err}"); assert!(err.contains("example__uint32.py"), "{err}"); } + + #[test] + fn missing_layout_identity_keeps_namespace_qualification() { + let _layout = install_python_module_layout([PythonTypeIdentity { + namespace: "Microsoft.UI.Dispatching".into(), + name: "Other".into(), + }]) + .unwrap(); + assert_eq!( + python_module_name("Windows.System", "DispatcherQueue"), + "windows__system__dispatcher_queue" + ); + } + + #[test] + fn missing_identity_reuses_unique_compatible_name() { + let _layout = install_python_module_layout([PythonTypeIdentity { + namespace: "Microsoft.Graphics.DirectX".into(), + name: "DirectXPixelFormat".into(), + }]) + .unwrap(); + assert_eq!( + python_module_name("Windows.Graphics.DirectX", "DirectXPixelFormat"), + "microsoft__graphics__direct_x__direct_x_pixel_format" + ); + } } diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/signature.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/signature.rs index 5232e7cd..2408dcc1 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/signature.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/signature.rs @@ -912,7 +912,7 @@ mod tests { let known = HashSet::from(["Mode".to_string()]); assert_eq!( py_type_guard("value", &enum_type("Mode", false), &known), - "isinstance(value, _dynwinrt_symbol('mode', 'Mode'))" + "isinstance(value, _dynwinrt_symbol('contoso__mode', 'Mode'))" ); assert_eq!( py_type_guard("value", &enum_type("Mode", false), &HashSet::new()), diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/stub_helpers.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/stub_helpers.rs index 07a8931d..25a4fcae 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/stub_helpers.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/stub_helpers.rs @@ -13,8 +13,8 @@ use super::naming::{python_module_name, to_snake_case}; use super::native_types::{FoundationType, foundation_type}; use super::structs::{py_struct_field_read_type, py_struct_field_type}; use super::type_helpers::{ - py_delegate_callable_type, py_factory_return_type, py_method_return_type, py_output_type, - py_param_list, py_param_type_safe, + method_pydoc_with_indent, py_delegate_callable_type, py_factory_return_type, + py_method_return_type, py_output_type, py_param_list, py_param_type_safe, }; use crate::codegen::winrt::shared::imports::ireference_inner_type; @@ -47,6 +47,28 @@ pub(super) fn py_struct_export_names(s: &TypeMeta) -> Vec { } } +pub(super) fn generate_struct_stub_imports(structs: &[TypeMeta]) -> String { + let mut imports = structs + .iter() + .filter_map(|typ| { + let TypeMeta::Struct { + namespace, name, .. + } = typ + else { + return None; + }; + Some(format!( + "from .{} import {} # noqa: F401\n", + python_module_name(namespace, name), + py_struct_export_names(typ).join(", ") + )) + }) + .collect::>(); + imports.sort(); + imports.dedup(); + imports.concat() +} + pub(super) fn emit_struct_stub(s: &TypeMeta) -> String { if let Some(kind) = foundation_type(s) { let TypeMeta::Struct { name, .. } = s else { @@ -193,6 +215,7 @@ pub(super) fn emit_method_stub( indent_spaces: usize, event_has_remove: bool, property_has_getter: bool, + overrides_mutable_sequence: bool, ) -> String { emit_method_stub_named( method, @@ -202,9 +225,20 @@ pub(super) fn emit_method_stub( None, event_has_remove, property_has_getter, + overrides_mutable_sequence, ) } +fn emit_documented_stub(out: &mut String, indent: &str, signature: &str, doc: &str, suffix: &str) { + if doc.is_empty() { + out.push_str(&format!("{indent}{signature}: ...{suffix}\n")); + } else { + out.push_str(&format!("{indent}{signature}:{suffix}\n")); + out.push_str(doc); + out.push_str(&format!("{indent} ...\n")); + } +} + pub(super) fn emit_method_stub_named( method: &MethodMeta, known_types: &HashSet, @@ -213,6 +247,7 @@ pub(super) fn emit_method_stub_named( name_override: Option<&str>, event_has_remove: bool, property_has_getter: bool, + overrides_mutable_sequence: bool, ) -> String { let indent = " ".repeat(indent_spaces); let in_params = get_in_params(method); @@ -230,6 +265,8 @@ pub(super) fn emit_method_stub_named( }; let mut out = String::new(); + let body_indent = format!("{indent} "); + let doc = method_pydoc_with_indent(method, &in_params, &body_indent); // Events if method.is_event_add { @@ -240,10 +277,16 @@ pub(super) fn emit_method_stub_named( let callback_sig = delegate_typ .map(|typ| py_delegate_callable_type(typ, known_types)) .unwrap_or_else(|| "Callable[..., object]".to_string()); - out.push_str(&format!( - "{indent}def on_{}(self, callback: {}) -> 'DynWinRTValue': ...\n", - event_name, callback_sig - )); + emit_documented_stub( + &mut out, + &indent, + &format!( + "def on_{}(self, callback: {}) -> 'DynWinRTValue'", + event_name, callback_sig + ), + &doc, + "", + ); if event_has_remove { out.push_str(&format!( "{indent}def subscribe_{}(self, callback: {}) -> Callable[[], None]: ...\n", @@ -258,10 +301,16 @@ pub(super) fn emit_method_stub_named( } if method.is_event_remove { let event_name = to_snake_case(method.name.strip_prefix("remove_").unwrap_or(&method.name)); - out.push_str(&format!( - "{indent}def off_{}(self, token: 'DynWinRTValue') -> None: ...\n", - event_name - )); + emit_documented_stub( + &mut out, + &indent, + &format!( + "def off_{}(self, token: 'DynWinRTValue') -> None", + event_name + ), + &doc, + "", + ); return out; } @@ -271,10 +320,13 @@ pub(super) fn emit_method_stub_named( .map(|typ| py_output_type(typ, known_types, delegate_type_names)) .unwrap_or_else(|| "None".to_string()); out.push_str(&format!("{indent}@builtins.property\n")); - out.push_str(&format!( - "{indent}def {}(self) -> {}: ...\n", - prop_name, py_return - )); + emit_documented_stub( + &mut out, + &indent, + &format!("def {}(self) -> {}", prop_name, py_return), + &doc, + "", + ); } else if method.is_property_setter { let prop_name = to_snake_case(method.name.strip_prefix("put_").unwrap_or(&method.name)); let param_type = if in_params @@ -290,15 +342,21 @@ pub(super) fn emit_method_stub_named( }; if property_has_getter { out.push_str(&format!("{indent}@{}.setter\n", prop_name)); - out.push_str(&format!( - "{indent}def {}(self, value: {}) -> None: ...\n", - prop_name, param_type - )); + emit_documented_stub( + &mut out, + &indent, + &format!("def {}(self, value: {}) -> None", prop_name, param_type), + &doc, + "", + ); } else { - out.push_str(&format!( - "{indent}def set_{}(self, value: {}) -> None: ...\n", - prop_name, param_type - )); + emit_documented_stub( + &mut out, + &indent, + &format!("def set_{}(self, value: {}) -> None", prop_name, param_type), + &doc, + "", + ); } } else { let py_params = py_param_list(&in_params, known_types, delegate_type_names); @@ -311,10 +369,26 @@ pub(super) fn emit_method_stub_named( } else { format!("self, {}", py_params) }; - out.push_str(&format!( - "{indent}def {}({}) -> {}: ...\n", - method_name, self_and_params, py_return - )); + // WinRT vectors may reject null on mutation while returning null + // interface elements. That asymmetric native contract cannot satisfy + // MutableSequence[T | None]'s append signature exactly. + let override_ignore = if overrides_mutable_sequence + && method_name == "append" + && in_params.first().is_some_and(|param| { + py_param_type_safe(¶m.typ, known_types) + != super::type_helpers::py_return_type_safe(Some(¶m.typ), known_types) + }) { + " # type: ignore[override]" + } else { + "" + }; + emit_documented_stub( + &mut out, + &indent, + &format!("def {}({}) -> {}", method_name, self_and_params, py_return), + &doc, + override_ignore, + ); } out @@ -355,6 +429,7 @@ pub(super) fn emit_static_method_stub_named( }; let mut out = String::new(); + let doc = method_pydoc_with_indent(method, &in_params, " "); if is_factory || !method.is_property_getter || !in_params.is_empty() { let method_name = name_override @@ -362,23 +437,32 @@ pub(super) fn emit_static_method_stub_named( .unwrap_or_else(|| to_snake_case(&method.name)); out.push_str(" @staticmethod\n"); if py_params.is_empty() { - out.push_str(&format!( - " def {}() -> {}: ...\n", - method_name, py_return - )); + emit_documented_stub( + &mut out, + " ", + &format!("def {}() -> {}", method_name, py_return), + &doc, + "", + ); } else { - out.push_str(&format!( - " def {}({}) -> {}: ...\n", - method_name, py_params, py_return - )); + emit_documented_stub( + &mut out, + " ", + &format!("def {}({}) -> {}", method_name, py_params, py_return), + &doc, + "", + ); } } else { let prop_name = to_snake_case(method.name.strip_prefix("get_").unwrap_or(&method.name)); out.push_str(" @classmethod\n"); - out.push_str(&format!( - " def get_{}(cls) -> {}: ...\n", - prop_name, py_return - )); + emit_documented_stub( + &mut out, + " ", + &format!("def get_{}(cls) -> {}", prop_name, py_return), + &doc, + "", + ); } out } @@ -416,6 +500,7 @@ mod tests { 4, true, true, + false, ); assert!(code.contains("def on_changed(")); assert!(code.contains("-> 'DynWinRTValue': ...")); @@ -432,9 +517,49 @@ mod tests { 4, false, true, + false, ); assert!(code.contains("def on_changed(")); assert!(!code.contains("subscribe_changed")); assert!(!code.contains("once_changed")); } + + #[test] + fn append_ignores_only_nullable_reference_override_mismatches() { + let append = |typ| MethodMeta { + name: "Append".into(), + raw_name: "Append".into(), + params: vec![ParamMeta { + name: "value".into(), + typ, + direction: ParamDirection::In, + }], + ..Default::default() + }; + let reference = emit_method_stub( + &append(TypeMeta::Interface { + namespace: "Contoso".into(), + name: "Widget".into(), + iid: "11111111-1111-1111-1111-111111111111".into(), + }), + &HashSet::from(["Widget".into()]), + &HashSet::new(), + 4, + false, + true, + true, + ); + let scalar = emit_method_stub( + &append(TypeMeta::I32), + &HashSet::new(), + &HashSet::new(), + 4, + false, + true, + true, + ); + + assert!(reference.contains("type: ignore[override]")); + assert!(!scalar.contains("type: ignore[override]")); + } } diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs index 7821bc10..65e43307 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs @@ -13,20 +13,29 @@ use crate::types::{TypeKind, TypeMeta}; use crate::codegen::winrt::extensions::winui; use crate::codegen::winrt::shared::imports::{ - collect_iface_type_imports, collect_type_imports, collect_used_generics_from_class, - collect_used_generics_from_methods, + collect_iface_type_imports, collect_struct_field_type_imports, collect_type_imports, + collect_used_generics_from_class, collect_used_generics_from_methods, + collect_used_generics_from_type, }; use crate::codegen::winrt::shared::structs::{ collect_used_structs_from_class, collect_used_structs_from_iface, + collect_used_structs_from_struct, }; -use super::collections::{abc_name, class_interface, interface_kind, observable_vector_name}; -use super::naming::{is_py_reserved, python_module_name, to_snake_case, to_snake_case_filename}; +use super::collections::{ + CollectionKind, abc_name, class_interface, interface_kind, observable_vector_name, +}; +use super::naming::{ + is_py_reserved, python_module_layout_installed, python_module_name, + python_public_qualified_module_name, to_snake_case, to_snake_case_filename, +}; +use super::native_types::foundation_type; use super::shared::reorder_getters_before_setters; use super::signature::py_dynwinrt_type; use super::stub_helpers::{ emit_method_stub, emit_method_stub_named, emit_static_method_stub, - emit_static_method_stub_named, emit_struct_stub, format_py_type_import, py_struct_export_names, + emit_static_method_stub_named, emit_struct_stub, format_py_type_import, + generate_struct_stub_imports, py_struct_export_names, }; use super::type_helpers::methods_have_async_output; @@ -34,26 +43,87 @@ const HEADER: &str = "# Generated by dynwinrt-codegen — do not edit\n"; const FUTURE_ANNOTATIONS: &str = "from __future__ import annotations\n"; const IMPORT_LINE: &str = "\ import builtins -from collections.abc import ( +from ._typing import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from uuid import UUID -from typing import overload -from dynwinrt import ( - DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, + UUID, WinGUID, datetime, overload, timedelta, + DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, )\n"; const ASYNC_IMPORT_LINE: &str = "from dynwinrt import WinRTAsync, WinRTAsyncWithProgress\n"; +pub fn generate_typing_support_module() -> String { + format!( + "{HEADER}\ +from collections.abc import (\n\ + Callable as Callable, Iterable as Iterable, Iterator as Iterator,\n\ + Mapping as Mapping, MutableMapping as MutableMapping,\n\ + MutableSequence as MutableSequence, Sequence as Sequence,\n\ +)\n\ +from datetime import datetime as datetime, timedelta as timedelta\n\ +from typing import overload as overload\n\ +from uuid import UUID as UUID\n\ +from dynwinrt import (\n\ + DynWinRTType as DynWinRTType, DynWinRTValue as DynWinRTValue,\n\ + DynWinRTArray as DynWinRTArray, DynWinRTStruct as DynWinRTStruct,\n\ + DynWinRtDelegate as DynWinRtDelegate, WinGUID as WinGUID,\n\ +)\n" + ) +} + +pub fn generate_runtime_support_stub() -> String { + format!("{HEADER}{FUTURE_ANNOTATIONS}") +} + +pub fn generate_struct_stub(s: &TypeMeta) -> Option { + let TypeMeta::Struct { name, .. } = s else { + return None; + }; + if name == "HResult" { + return None; + } + + let mut out = String::new(); + out.push_str(HEADER); + out.push_str(FUTURE_ANNOTATIONS); + out.push_str(IMPORT_LINE); + out.push_str(&generate_struct_stub_imports( + &collect_used_structs_from_struct(s), + )); + + let mut imports = collect_struct_field_type_imports(s) + .into_iter() + .map(|type_ref| format_py_type_import(&type_ref.namespace, &type_ref.name, type_ref.kind)) + .collect::>(); + imports.extend(collect_used_generics_from_type(s).into_iter().map(|name| { + format!( + "from .{} import {} # noqa: F401\n", + to_snake_case_filename(&name), + name + ) + })); + imports.sort(); + imports.dedup(); + out.push_str(&imports.concat()); + out.push_str(&emit_struct_stub(s)); + Some(out) +} + /// Generate a `.pyi` stub for an enum. Returns `None` for non-enum TypeMeta. pub fn generate_enum_stub(en: &TypeMeta) -> Option { - let (name, members, is_flags) = match en { + let (name, members, is_flags, doc, deprecated) = match en { TypeMeta::Enum { name, members, is_flags, + doc, + deprecated, .. - } => (name, members, *is_flags), + } => ( + name, + members, + *is_flags, + doc.as_deref(), + deprecated.as_deref(), + ), _ => return None, }; let mut out = String::new(); @@ -61,6 +131,14 @@ pub fn generate_enum_stub(en: &TypeMeta) -> Option { let enum_base = if is_flags { "IntFlag" } else { "IntEnum" }; out.push_str(&format!("from enum import {enum_base}\n\n\n")); out.push_str(&format!("class {}({enum_base}):\n", name)); + out.push_str(&super::docs::format_pydoc( + &crate::codegen::winrt::shared::docs::DocText { + summary: doc, + deprecated, + ..Default::default() + }, + " ", + )); if members.is_empty() { out.push_str(" pass\n"); } else { @@ -112,6 +190,9 @@ pub fn generate_interface_stub( { out.push_str("from typing import Callable\n"); } + if python_module_layout_installed() { + out.push_str(&generate_struct_stub_imports(&used_structs)); + } out.push('\n'); let delegate_names = @@ -171,9 +252,11 @@ pub fn generate_interface_stub( } out.push('\n'); - for s in &used_structs { - out.push_str(&emit_struct_stub(s)); - out.push('\n'); + if !python_module_layout_installed() { + for s in &used_structs { + out.push_str(&emit_struct_stub(s)); + out.push('\n'); + } } let collection_base = @@ -198,6 +281,14 @@ pub fn generate_interface_stub( } else { out.push_str(&format!("\nclass {}:\n", iface.name)); } + out.push_str(&super::docs::format_pydoc( + &crate::codegen::winrt::shared::docs::DocText { + summary: iface.doc.as_deref(), + deprecated: iface.deprecated.as_deref(), + ..Default::default() + }, + " ", + )); out.push_str(" def __init__(self, obj: DynWinRTValue) -> None: ...\n"); out.push_str(&collection_protocol_stubs(iface, known_types, 4)); if !iface.iid.is_empty() || iface.generic_piid.is_some() { @@ -287,6 +378,7 @@ pub fn generate_interface_stub( 4, event_has_remove, property_has_getter, + collection_kind == Some(CollectionKind::MutableSequence), )); } @@ -340,6 +432,9 @@ pub fn generate_class_stub( } else if winui::is_dispatcher_queue(class) { out.push_str("from typing import TypeVar\n"); } + if python_module_layout_installed() { + out.push_str(&generate_struct_stub_imports(&used_structs)); + } out.push('\n'); if winui::is_dispatcher_queue(class) { out.push_str("_DispatchResultT = TypeVar('_DispatchResultT')\n\n"); @@ -358,6 +453,7 @@ pub fn generate_class_stub( )); } + let mut imported_names: HashSet = HashSet::new(); let collection_names = collect_used_generics_from_class(class); for cname in &collection_names { if !delegate_names.contains(cname) { @@ -366,6 +462,25 @@ pub fn generate_class_stub( "from .{} import {} # noqa: F401\n", module, cname )); + imported_names.insert(cname.clone()); + } + } + for iface in class.all_interfaces() { + if iface.generic_piid.as_deref() == Some(super::collections::IOBSERVABLE_VECTOR_PIID) { + if imported_names.insert(iface.name.clone()) { + let module = to_snake_case_filename(&iface.name); + out.push_str(&format!( + "from .{module} import {} # noqa: F401\n", + iface.name + )); + } + let event_args = "IVectorChangedEventArgs"; + if imported_names.insert(event_args.into()) { + let module = to_snake_case_filename(event_args); + out.push_str(&format!( + "from .{module} import {event_args} # noqa: F401\n" + )); + } } } @@ -378,7 +493,6 @@ pub fn generate_class_stub( )); } - let mut imported_names: HashSet = HashSet::new(); let imports = collect_type_imports(class); let mut sorted_imports: Vec<_> = imports.iter().collect(); sorted_imports @@ -421,17 +535,23 @@ pub fn generate_class_stub( } // IID constants (declarations only) + let mut declared_iids = HashSet::new(); for iface in class.all_interfaces() { let iid_name = format!("IID_{}", iface.name); - if !iface.iid.is_empty() && !imported_names.contains(&iid_name) { + if !iface.iid.is_empty() + && !imported_names.contains(&iid_name) + && declared_iids.insert(iid_name.clone()) + { out.push_str(&format!("{}: WinGUID\n", iid_name)); } } out.push('\n'); - for s in &used_structs { - out.push_str(&emit_struct_stub(s)); - out.push('\n'); + if !python_module_layout_installed() { + for s in &used_structs { + out.push_str(&emit_struct_stub(s)); + out.push('\n'); + } } let collection_base = collection_iface @@ -455,6 +575,14 @@ pub fn generate_class_stub( } else { out.push_str(&format!("\nclass {}:\n", class.name)); } + out.push_str(&super::docs::format_pydoc( + &crate::codegen::winrt::shared::docs::DocText { + summary: class.doc.as_deref(), + deprecated: class.deprecated.as_deref(), + ..Default::default() + }, + " ", + )); out.push_str(&emit_constructor_stubs(class, known_types, &delegate_names)); if let Some(collection_iface) = collection_iface { out.push_str(&collection_protocol_stubs(collection_iface, known_types, 4)); @@ -633,6 +761,7 @@ pub fn generate_class_stub( 4, event_has_remove, property_has_getter, + collection_kind == Some(CollectionKind::MutableSequence), )); } // IClosable -> close() @@ -746,6 +875,7 @@ pub fn generate_class_stub( 4, event_has_remove, property_has_getter, + interface_kind(req_iface) == Some(CollectionKind::MutableSequence), )); } } @@ -912,7 +1042,7 @@ fn emit_constructor_stubs( )); } if overloads.is_empty() { - out.push_str(" def __new__(cls, _not_constructible: NoReturn) -> NoReturn: ...\n"); + out.push_str(" def __init__(self, _not_constructible: NoReturn) -> None: ...\n"); return out; } overloads.sort_by(|left, right| super::overloads::cmp_python_dispatch_params(left, right)); @@ -999,6 +1129,7 @@ fn emit_instance_stub_group( indent_spaces: usize, event_has_remove: bool, property_has_getter: bool, + overrides_mutable_sequence: bool, ) -> String { let mut ordered_methods = methods.iter().copied().collect::>(); ordered_methods @@ -1012,6 +1143,7 @@ fn emit_instance_stub_group( indent_spaces, event_has_remove, property_has_getter, + overrides_mutable_sequence, ); } let names = super::overloads::method_names(ordered_methods.iter().copied()); @@ -1030,6 +1162,7 @@ fn emit_instance_stub_group( Some(&public_name), event_has_remove, property_has_getter, + overrides_mutable_sequence, ) ) }) @@ -1107,26 +1240,11 @@ pub fn generate_index_stub( sorted_classes.sort_by(|a, b| a.name.cmp(&b.name)); for class in sorted_classes { if seen.insert(class.name.clone()) { - let struct_names: Vec<_> = collect_used_structs_from_class(class) - .iter() - .flat_map(|s| py_struct_export_names(s)) - .filter(|n| seen.insert(n.clone())) - .collect(); let module = python_module_name(&class.namespace, &class.name); - if struct_names.is_empty() { - out.push_str(&format!( - "from .{} import {} as {}\n", - module, class.name, class.name - )); - } else { - out.push_str(&format!( - "from .{} import {} as {}, {}\n", - module, - class.name, - class.name, - struct_names.join(", ") - )); - } + out.push_str(&format!( + "from .{} import {} as {}\n", + module, class.name, class.name + )); } } @@ -1179,3 +1297,132 @@ pub fn generate_index_stub( } out } + +pub fn generate_public_index_stub( + classes: &[ClassMeta], + interfaces: &[InterfaceMeta], + enums: &[TypeMeta], +) -> String { + let mut out = String::from(HEADER); + let mut seen = HashSet::new(); + + let mut classes = classes.iter().collect::>(); + classes.sort_by(|left, right| left.name.cmp(&right.name)); + for class in classes { + if seen.insert(class.name.clone()) { + out.push_str(&format!( + "from .{} import {} as {}\n", + python_public_qualified_module_name(&class.namespace, &class.name), + class.name, + class.name + )); + } + } + + let mut interfaces = interfaces.iter().collect::>(); + interfaces.sort_by(|left, right| left.name.cmp(&right.name)); + for interface in interfaces { + let is_delegate = interface + .methods + .iter() + .any(|method| method.name == ".ctor") + && interface + .methods + .iter() + .any(|method| method.name == "Invoke"); + if !is_delegate && seen.insert(interface.name.clone()) { + out.push_str(&format!( + "from .{} import {} as {}\n", + python_public_qualified_module_name(&interface.namespace, &interface.name), + interface.name, + interface.name + )); + } + } + + let mut enums = enums.iter().collect::>(); + enums.sort_by_key(|typ| match typ { + TypeMeta::Enum { name, .. } => name.as_str(), + _ => "", + }); + for typ in enums { + let TypeMeta::Enum { + namespace, name, .. + } = typ + else { + continue; + }; + if seen.insert(name.clone()) { + out.push_str(&format!( + "from .{} import {} as {}\n", + python_public_qualified_module_name(namespace, name), + name, + name + )); + } + } + out +} + +pub fn generate_struct_index_stub(structs: &[TypeMeta]) -> String { + let mut out = String::from(HEADER); + let mut seen = HashSet::new(); + let mut sorted = structs.iter().collect::>(); + sorted.sort_by_key(|typ| match typ { + TypeMeta::Struct { + namespace, name, .. + } => format!("{namespace}.{name}"), + _ => String::new(), + }); + for typ in sorted { + let TypeMeta::Struct { + namespace, name, .. + } = typ + else { + continue; + }; + if !seen.insert((namespace, name)) { + continue; + } + out.push_str(&format!( + "from .{} import {}\n", + python_module_name(namespace, name), + py_struct_export_names(typ) + .into_iter() + .map(|name| format!("{name} as {name}")) + .collect::>() + .join(", ") + )); + } + out +} + +pub fn generate_public_struct_index_stub(structs: &[TypeMeta]) -> String { + let mut out = String::from(HEADER); + let mut seen = HashSet::new(); + let mut sorted = structs.iter().collect::>(); + sorted.sort_by_key(|typ| match typ { + TypeMeta::Struct { + namespace, name, .. + } => format!("{namespace}.{name}"), + _ => String::new(), + }); + for typ in sorted { + let TypeMeta::Struct { + namespace, name, .. + } = typ + else { + continue; + }; + if foundation_type(typ).is_some() || !seen.insert((namespace, name)) { + continue; + } + out.push_str(&format!( + "from .{} import {} as {}\n", + python_public_qualified_module_name(namespace, name), + name, + name + )); + } + out +} diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/type_helpers.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/type_helpers.rs index 405529d4..b8fa64de 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/type_helpers.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/type_helpers.rs @@ -22,6 +22,14 @@ use super::native_types::{FoundationType, foundation_type}; /// doc fields are populated, preserving byte-identity for metadata without /// sibling .xml files. pub(super) fn method_pydoc(method: &MethodMeta, in_params: &[&crate::meta::ParamMeta]) -> String { + method_pydoc_with_indent(method, in_params, " ") +} + +pub(super) fn method_pydoc_with_indent( + method: &MethodMeta, + in_params: &[&crate::meta::ParamMeta], + indent: &str, +) -> String { if method.doc.is_none() && method.deprecated.is_none() && method.returns_doc.is_none() @@ -43,7 +51,7 @@ pub(super) fn method_pydoc(method: &MethodMeta, in_params: &[&crate::meta::Param returns: method.returns_doc.as_deref(), params: params_refs, }; - format_pydoc(&doc, " ") + format_pydoc(&doc, indent) } // ====================================================================== diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/shared/imports.rs b/tools/dynwinrt-codegen/src/codegen/winrt/shared/imports.rs index b35059e3..06cc0966 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/shared/imports.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/shared/imports.rs @@ -41,39 +41,48 @@ pub(crate) fn collect_used_generics_from_methods(methods: &[MethodMeta]) -> Vec< collect_used_generics_from_methods_inner(&refs) } -/// Shared implementation for collecting generic names from method references. -fn collect_used_generics_from_methods_inner(methods: &[&MethodMeta]) -> Vec { - let mut names: HashSet = HashSet::new(); - fn visit(typ: &TypeMeta, names: &mut HashSet) { - match typ { - TypeMeta::Parameterized { name, args, .. } => { - names.insert(crate::meta::make_parameterized_name(name, args)); - for arg in args { - visit(arg, names); - } - } - TypeMeta::AsyncOperation(inner) | TypeMeta::AsyncActionWithProgress(inner) => { - visit(inner, names) - } - TypeMeta::AsyncOperationWithProgress(r, p) => { - visit(r, names); - visit(p, names); +fn visit_used_generics(typ: &TypeMeta, names: &mut HashSet) { + match typ { + TypeMeta::Parameterized { name, args, .. } => { + names.insert(crate::meta::make_parameterized_name(name, args)); + for arg in args { + visit_used_generics(arg, names); } - TypeMeta::Array(inner) => visit(inner, names), - TypeMeta::Struct { fields, .. } => { - for field in fields { - visit(&field.typ, names); - } + } + TypeMeta::AsyncOperation(inner) | TypeMeta::AsyncActionWithProgress(inner) => { + visit_used_generics(inner, names) + } + TypeMeta::AsyncOperationWithProgress(r, p) => { + visit_used_generics(r, names); + visit_used_generics(p, names); + } + TypeMeta::Array(inner) => visit_used_generics(inner, names), + TypeMeta::Struct { fields, .. } => { + for field in fields { + visit_used_generics(&field.typ, names); } - _ => {} } + _ => {} } +} + +pub(crate) fn collect_used_generics_from_type(typ: &TypeMeta) -> Vec { + let mut names = HashSet::new(); + visit_used_generics(typ, &mut names); + let mut sorted = names.into_iter().collect::>(); + sorted.sort(); + sorted +} + +/// Shared implementation for collecting generic names from method references. +fn collect_used_generics_from_methods_inner(methods: &[&MethodMeta]) -> Vec { + let mut names: HashSet = HashSet::new(); for m in methods { for p in &m.params { - visit(&p.typ, &mut names); + visit_used_generics(&p.typ, &mut names); } if let Some(ref rt) = m.return_type { - visit(rt, &mut names); + visit_used_generics(rt, &mut names); } } let mut sorted: Vec = names.into_iter().collect(); @@ -192,6 +201,17 @@ pub(crate) fn collect_type_imports(class: &ClassMeta) -> HashSet { imports } +pub(crate) fn collect_struct_field_type_imports(typ: &TypeMeta) -> HashSet { + let TypeMeta::Struct { name, fields, .. } = typ else { + return HashSet::new(); + }; + let mut imports = HashSet::new(); + for field in fields { + visit_type_for_imports(&field.typ, name, false, &mut imports); + } + imports +} + // ====================================================================== // Parameter helpers // ====================================================================== diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/shared/structs.rs b/tools/dynwinrt-codegen/src/codegen/winrt/shared/structs.rs index 2d907116..e61c5b17 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/shared/structs.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/shared/structs.rs @@ -13,7 +13,7 @@ use crate::types::TypeMeta; // ====================================================================== /// Recursively collect non-HResult struct types from a type tree. -fn collect_used_structs_from_type( +pub(crate) fn collect_used_structs_from_type( typ: &TypeMeta, seen: &mut HashSet, result: &mut Vec, @@ -55,6 +55,23 @@ fn collect_used_structs_from_type( } } +pub(crate) fn collect_used_structs_from_struct(typ: &TypeMeta) -> Vec { + let TypeMeta::Struct { + namespace, + name, + fields, + } = typ + else { + return Vec::new(); + }; + let mut seen = HashSet::from([format!("{namespace}.{name}")]); + let mut result = Vec::new(); + for field in fields { + collect_used_structs_from_type(&field.typ, &mut seen, &mut result); + } + result +} + pub(crate) fn collect_used_structs_from_class(class: &ClassMeta) -> Vec { let mut seen = HashSet::new(); let mut result = Vec::new(); diff --git a/tools/dynwinrt-codegen/src/main.rs b/tools/dynwinrt-codegen/src/main.rs index bfe2b110..722f0ff9 100644 --- a/tools/dynwinrt-codegen/src/main.rs +++ b/tools/dynwinrt-codegen/src/main.rs @@ -581,7 +581,15 @@ fn run() -> Result<(), String> { winui::add_implicit_classes(&winmd, &mut classes); let mut implicit_interfaces = Vec::new(); winui::add_implicit_interfaces(&winmd, &classes, &mut implicit_interfaces); - generate_for_types( + let existing_python_identities = if lang == "py" && !dry_run { + read_python_type_inventory(output_dir)? + .into_iter() + .map(|typ| typ.identity) + .collect::>() + } else { + Vec::new() + }; + let (_, _, _, shared_interfaces) = generate_for_types( &winmd, output_dir, classes.clone(), @@ -591,6 +599,7 @@ fn run() -> Result<(), String> { &lang, pyi, &doc_table, + &existing_python_identities, )?; // Write (or append to) the index file for the output directory @@ -662,6 +671,7 @@ fn run() -> Result<(), String> { pyi, true, )?; + record_python_supplemental_types(output_dir, &shared_interfaces)?; } else { // JS: index.js + index.d.ts are pure re-exports and identical, so we // round-trip incremental appends by reading back index.d.ts (which @@ -707,10 +717,9 @@ fn run() -> Result<(), String> { } }; - let mut total_classes = 0usize; - let mut total_interfaces = 0usize; - let mut total_enums = 0usize; - + let mut selected_classes = Vec::new(); + let mut selected_interfaces = Vec::new(); + let mut selected_enums = Vec::new(); for ns in &namespaces { if let Some(interface) = com_metadata::first_classic_com_interface_in_namespace(&winmd, ns) @@ -722,29 +731,25 @@ fn run() -> Result<(), String> { Classic-COM ABI pipeline." )); } - let mut classes = meta::parse_namespace(&winmd, ns); - let mut interfaces = meta::parse_interfaces(&winmd, ns); - let mut enums = meta::parse_enums(&winmd, ns); - winui::add_implicit_classes(&winmd, &mut classes); - winui::add_implicit_interfaces(&winmd, &classes, &mut interfaces); - for c in classes.iter_mut() { - doc_table.apply_to_class(c); - } - for i in interfaces.iter_mut() { - doc_table.apply_to_interface(i); - } - for e in enums.iter_mut() { - doc_table.apply_to_enum(e); - } - - let (nc, ni, ne) = generate_for_types( - &winmd, output_dir, classes, interfaces, enums, dry_run, &lang, pyi, + selected_classes.extend(meta::parse_namespace(&winmd, ns)); + selected_interfaces.extend(meta::parse_interfaces(&winmd, ns)); + selected_enums.extend(meta::parse_enums(&winmd, ns)); + } + winui::add_implicit_classes(&winmd, &mut selected_classes); + winui::add_implicit_interfaces(&winmd, &selected_classes, &mut selected_interfaces); + let (total_classes, total_interfaces, total_enums, shared_interfaces) = + generate_for_types( + &winmd, + output_dir, + selected_classes, + selected_interfaces, + selected_enums, + dry_run, + &lang, + pyi, &doc_table, + &[], )?; - total_classes += nc; - total_interfaces += ni; - total_enums += ne; - } // Generate index file combining everything if !dry_run @@ -818,6 +823,7 @@ fn run() -> Result<(), String> { pyi, false, )?; + record_python_supplemental_types(output_dir, &shared_interfaces)?; } else { let index_code = typescript::generate_index(&all_classes, &all_interfaces, &all_enums); @@ -866,7 +872,8 @@ fn generate_for_types( lang: &str, pyi: bool, doc_table: &DocTable, -) -> Result<(usize, usize, usize), String> { + existing_python_identities: &[python::PythonTypeIdentity], +) -> Result<(usize, usize, usize, Vec), String> { let deps = meta::resolve_dependencies(winmd, &classes, &interfaces, &enums); let mut all_classes = classes; let mut all_interfaces = interfaces; @@ -913,13 +920,6 @@ fn generate_for_types( .filter(|i| is_emittable_iface(i)) .cloned() .collect(); - let python_layout = if lang == "py" { - Some(python::install_python_module_layout( - python_type_identities(&all_classes, &emittable_interfaces, &all_enums), - )?) - } else { - None - }; let mut known_types: HashSet = HashSet::new(); for c in &all_classes { @@ -979,9 +979,39 @@ fn generate_for_types( .filter(|(_, (_, count))| *count >= 2) .map(|(_, (iface, _))| (*iface).clone()) .collect(); + if lang == "py" { + let mut struct_interfaces = emittable_interfaces.clone(); + struct_interfaces.extend(shared_interfaces.iter().cloned()); + python::validate_struct_symbol_uniqueness(&all_classes, &struct_interfaces)?; + } for iface in &shared_interfaces { known_types.insert(iface.name.clone()); } + if lang == "py" { + let mut struct_interfaces = emittable_interfaces.clone(); + struct_interfaces.extend(shared_interfaces.iter().cloned()); + for typ in python::package_structs(&all_classes, &struct_interfaces) { + if let TypeMeta::Struct { + namespace, name, .. + } = typ + { + known_types.insert(name.clone()); + known_types.insert(format!("{namespace}.{name}")); + } + } + } + + let python_layout = if lang == "py" { + Some(install_python_generation_layout( + &all_classes, + &emittable_interfaces, + &all_enums, + &shared_interfaces, + existing_python_identities, + )?) + } else { + None + }; let (delegate_signatures, delegate_sig_refs, delegate_param_wraps) = project::build_delegate_signatures(&all_interfaces, &delegate_type_names, &known_types); @@ -991,7 +1021,7 @@ fn generate_for_types( generate_py_files( output_dir, &all_classes, - &all_interfaces, + &emittable_interfaces, &all_enums, &shared_interfaces, &known_types, @@ -1017,7 +1047,12 @@ fn generate_for_types( drop(python_layout); } - Ok((all_classes.len(), all_interfaces.len(), all_enums.len())) + Ok(( + all_classes.len(), + all_interfaces.len(), + all_enums.len(), + shared_interfaces, + )) } fn python_type_identities( @@ -1050,9 +1085,27 @@ fn python_type_identities( name: name.clone(), }) })); + identities.extend( + python::package_struct_identities(classes, interfaces) + .into_iter() + .map(|(namespace, name)| python::PythonTypeIdentity { namespace, name }), + ); identities } +fn install_python_generation_layout( + classes: &[meta::ClassMeta], + interfaces: &[meta::InterfaceMeta], + enums: &[TypeMeta], + supplemental_interfaces: &[meta::InterfaceMeta], + existing_identities: &[python::PythonTypeIdentity], +) -> Result { + let mut identities = python_type_identities(classes, interfaces, enums); + identities.extend(python_type_identities(&[], supplemental_interfaces, &[])); + identities.extend_from_slice(existing_identities); + python::install_python_module_layout(identities) +} + fn validate_unique_class_output_names(classes: &[meta::ClassMeta]) -> Result<(), String> { let mut full_name_by_short_name: HashMap<&str, &str> = HashMap::new(); for class in classes { @@ -2085,30 +2138,46 @@ fn generate_py_files( ) -> Result<(), String> { use dynwinrt_codegen::codegen::python_stub; - for iface in shared_interfaces { - let code = python::generate_interface(iface, known_types, delegate_type_names); - let module = python::python_module_name(&iface.namespace, &iface.name); - let filepath = output_dir.join(format!("{module}.py")); - write_file(&filepath, &code)?; - println!("Generated shared {}", filepath.display()); - if pyi { - let stub = - python_stub::generate_interface_stub(iface, known_types, delegate_type_names); - let p = output_dir.join(format!("{module}.pyi")); - write_file(&p, &stub)?; - } + write_file( + &output_dir.join("_runtime.py"), + &python::generate_runtime_support_module(), + )?; + if pyi { + write_file( + &output_dir.join("_runtime.pyi"), + &python_stub::generate_runtime_support_stub(), + )?; + write_file( + &output_dir.join("_typing.pyi"), + &python_stub::generate_typing_support_module(), + )?; } - for iface in all_interfaces { - let code = python::generate_interface(iface, known_types, delegate_type_names); - let module = python::python_module_name(&iface.namespace, &iface.name); + + let mut generated_modules = HashSet::new(); + let mut struct_interfaces = all_interfaces.to_vec(); + struct_interfaces.extend_from_slice(shared_interfaces); + let structs = python::package_structs(all_classes, &struct_interfaces); + + // A runtime class owns its public identity when metadata also exposes an + // interface or enum with the same namespace/name. Generate in precedence + // order and render each final module exactly once. + for class in all_classes { + let module = python::python_module_name(&class.namespace, &class.name); + if !generated_modules.insert(module.clone()) { + continue; + } + let code = python::generate_class(class, known_types, delegate_type_names, shared_iids); let filepath = output_dir.join(format!("{module}.py")); write_file(&filepath, &code)?; println!("Generated {}", filepath.display()); if pyi { - let stub = - python_stub::generate_interface_stub(iface, known_types, delegate_type_names); - let p = output_dir.join(format!("{module}.pyi")); - write_file(&p, &stub)?; + let stub = python_stub::generate_class_stub( + class, + known_types, + delegate_type_names, + shared_iids, + ); + write_file(&output_dir.join(format!("{module}.pyi")), &stub)?; } } for en in all_enums { @@ -2117,6 +2186,9 @@ fn generate_py_files( } = en { let module = python::python_module_name(namespace, name); + if !generated_modules.insert(module.clone()) { + continue; + } if let Some(code) = python::generate_enum(en) { let filepath = output_dir.join(format!("{module}.py")); write_file(&filepath, &code)?; @@ -2130,21 +2202,43 @@ fn generate_py_files( } } } - for class in all_classes { - let code = python::generate_class(class, known_types, delegate_type_names, shared_iids); - let module = python::python_module_name(&class.namespace, &class.name); + for iface in all_interfaces.iter().chain(shared_interfaces) { + let module = python::python_module_name(&iface.namespace, &iface.name); + if !generated_modules.insert(module.clone()) { + continue; + } + let code = python::generate_interface(iface, known_types, delegate_type_names); let filepath = output_dir.join(format!("{module}.py")); write_file(&filepath, &code)?; println!("Generated {}", filepath.display()); if pyi { - let stub = python_stub::generate_class_stub( - class, - known_types, - delegate_type_names, - shared_iids, - ); - let p = output_dir.join(format!("{module}.pyi")); - write_file(&p, &stub)?; + let stub = + python_stub::generate_interface_stub(iface, known_types, delegate_type_names); + write_file(&output_dir.join(format!("{module}.pyi")), &stub)?; + } + } + for typ in &structs { + let TypeMeta::Struct { + namespace, name, .. + } = typ + else { + continue; + }; + let module = python::python_module_name(namespace, name); + if !generated_modules.insert(module.clone()) { + return Err(format!( + "Python struct module `{namespace}.{name}` collides with another generated type" + )); + } + if let Some(code) = python::generate_struct(typ) { + let filepath = output_dir.join(format!("{module}.py")); + write_file(&filepath, &code)?; + println!("Generated {}", filepath.display()); + } + if pyi { + if let Some(stub) = python_stub::generate_struct_stub(typ) { + write_file(&output_dir.join(format!("{module}.pyi")), &stub)?; + } } } if pyi { @@ -2159,6 +2253,7 @@ struct PythonNamespaceGroup { classes: Vec, interfaces: Vec, enums: Vec, + structs: Vec, } #[derive(Clone, Debug, Eq, Hash, PartialEq)] @@ -2177,6 +2272,7 @@ fn write_python_package_indexes( ) -> Result<(), String> { use dynwinrt_codegen::codegen::python_stub; + let current_structs = python::package_structs(classes, interfaces); let current_types = python_generated_types(classes, interfaces, enums); let mut all_types = if append { read_python_type_inventory(output_dir)? @@ -2189,7 +2285,6 @@ fn write_python_package_indexes( let module_identities = all_types .iter() - .filter(|typ| typ.kind != "struct") .map(|typ| typ.identity.clone()) .collect::>(); let _layout = python::install_python_module_layout(module_identities.clone())?; @@ -2234,8 +2329,19 @@ fn write_python_package_indexes( .filter(|typ| matches!(typ, TypeMeta::Enum { name, .. } if counts[name] == 1)) .cloned() .collect::>(); + let root_structs = current_structs + .iter() + .filter(|typ| matches!(typ, TypeMeta::Struct { name, .. } if counts[name] == 1)) + .cloned() + .collect::>(); - let root_index = python::generate_index(&root_classes, &root_interfaces, &root_enums); + let mut root_index = + python::generate_public_index(&root_classes, &root_interfaces, &root_enums); + root_index.push_str( + python::generate_public_struct_index(&root_structs) + .strip_prefix(GENERATED_PYTHON_HEADER) + .unwrap_or_default(), + ); write_python_lazy_root_index( &output_dir.join("__init__.py"), &root_index, @@ -2243,8 +2349,13 @@ fn write_python_package_indexes( &suppressed_root_names, )?; if pyi { - let root_stub = - python_stub::generate_index_stub(&root_classes, &root_interfaces, &root_enums); + let mut root_stub = + python_stub::generate_public_index_stub(&root_classes, &root_interfaces, &root_enums); + root_stub.push_str( + python_stub::generate_public_struct_index_stub(&root_structs) + .strip_prefix(GENERATED_PYTHON_HEADER) + .unwrap_or_default(), + ); write_python_index( &output_dir.join("__init__.pyi"), &root_stub, @@ -2278,6 +2389,15 @@ fn write_python_package_indexes( .push(typ.clone()); } } + for typ in current_structs { + if let TypeMeta::Struct { namespace, .. } = &typ { + groups + .entry(namespace.clone()) + .or_default() + .structs + .push(typ); + } + } for (namespace, group) in groups { write_python_namespace_group(output_dir, &namespace, &group, pyi, append)?; @@ -2398,10 +2518,42 @@ fn write_python_namespace_group( )?; } } + for typ in &group.structs { + let TypeMeta::Struct { name, .. } = typ else { + continue; + }; + if !seen.insert(name.clone()) { + continue; + } + let runtime = python::generate_struct_index(std::slice::from_ref(typ)); + if runtime.lines().any(|line| line.starts_with("from .")) { + write_python_facade( + &package_dir, + &segments, + name, + &runtime, + "py", + &mut runtime_exports, + )?; + } + if pyi { + let stub = python_stub::generate_struct_index_stub(std::slice::from_ref(typ)); + if stub.lines().any(|line| line.starts_with("from .")) { + write_python_facade( + &package_dir, + &segments, + name, + &stub, + "pyi", + &mut stub_exports, + )?; + } + } + } let runtime_index = format!("{}{}", GENERATED_PYTHON_HEADER, runtime_exports.join("\n")); let suppressed_root_names = HashSet::new(); - write_python_index( + write_python_lazy_root_index( &package_dir.join("__init__.py"), &runtime_index, append, @@ -2435,6 +2587,14 @@ fn write_python_facade( .strip_prefix("from .") .and_then(|line| line.split_once(" import ")) .ok_or_else(|| format!("Generated index import for `{type_name}` is invalid"))?; + let exports = exports.split('#').next().unwrap_or(exports).trim(); + let exported_type = exports.split(',').any(|export| { + export + .trim() + .split_once(" as ") + .map_or_else(|| export.trim(), |(_, alias)| alias.trim()) + == type_name + }); let exports = if extension == "pyi" { exports .split(',') @@ -2452,14 +2612,24 @@ fn write_python_facade( exports.to_string() }; let relative_root = ".".repeat(namespace_segments.len() + 1); - let facade = + let mut facade = format!("{GENERATED_PYTHON_HEADER}from {relative_root}{source} import {exports}\n"); + if extension == "py" && exported_type { + facade.push_str(&format!("\n{type_name}.__module__ = __name__\n")); + } let public_module = python::python_public_module_name(type_name); write_file( &package_dir.join(format!("{public_module}.{extension}")), &facade, )?; - package_exports.push(format!("from .{public_module} import {exports}")); + if exported_type { + let package_export = if extension == "pyi" { + format!("from .{public_module} import {type_name} as {type_name}") + } else { + format!("from .{public_module} import {type_name}") + }; + package_exports.push(package_export); + } Ok(()) } @@ -2748,6 +2918,20 @@ fn write_python_type_inventory( ) } +fn record_python_supplemental_types( + output_dir: &Path, + interfaces: &[meta::InterfaceMeta], +) -> Result<(), String> { + if interfaces.is_empty() { + return Ok(()); + } + let mut types = read_python_type_inventory(output_dir)?; + types.extend(python_generated_types(&[], interfaces, &[])); + let mut seen = HashSet::new(); + types.retain(|typ| seen.insert(typ.clone())); + write_python_type_inventory(output_dir, &types) +} + #[cfg(test)] fn validate_python_public_paths( classes: &[meta::ClassMeta], @@ -3612,6 +3796,232 @@ mod tests { fs::remove_dir_all(output).unwrap(); } + #[test] + fn python_generation_emits_shared_runtime_and_typing_support() { + let output = test_directory("python-shared-support"); + fs::create_dir_all(&output).unwrap(); + + generate_py_files( + &output, + &[], + &[], + &[], + &[], + &HashSet::new(), + &HashSet::new(), + &HashSet::new(), + true, + ) + .unwrap(); + + assert!(output.join("_runtime.py").is_file()); + assert!(output.join("_runtime.pyi").is_file()); + assert!(output.join("_typing.pyi").is_file()); + assert!(output.join("py.typed").is_file()); + fs::remove_dir_all(output).unwrap(); + } + + #[test] + fn python_generation_uses_one_canonical_struct_module() { + let output = test_directory("python-canonical-struct"); + fs::create_dir_all(&output).unwrap(); + let point = TypeMeta::Struct { + namespace: "Windows.Foundation".into(), + name: "Point".into(), + fields: vec![ + dynwinrt_codegen::types::FieldMeta { + name: "X".into(), + typ: TypeMeta::F32, + }, + dynwinrt_codegen::types::FieldMeta { + name: "Y".into(), + typ: TypeMeta::F32, + }, + ], + }; + let interface = meta::InterfaceMeta { + name: "IWidget".into(), + namespace: "Contoso".into(), + iid: "11111111-1111-1111-1111-111111111111".into(), + methods: vec![meta::MethodMeta { + name: "SetPoint".into(), + raw_name: "SetPoint".into(), + params: vec![meta::ParamMeta { + name: "value".into(), + typ: point, + direction: meta::ParamDirection::In, + }], + ..Default::default() + }], + ..Default::default() + }; + let class = meta::ClassMeta { + name: "Widget".into(), + namespace: "Contoso".into(), + full_name: "Contoso.Widget".into(), + default_interface: Some(interface), + ..Default::default() + }; + let known_types = HashSet::from(["Point".into(), "Widget".into()]); + + { + let _layout = + install_python_generation_layout(std::slice::from_ref(&class), &[], &[], &[], &[]) + .unwrap(); + generate_py_files( + &output, + std::slice::from_ref(&class), + &[], + &[], + &[], + &known_types, + &HashSet::new(), + &HashSet::new(), + true, + ) + .unwrap(); + } + + let class_py = fs::read_to_string(output.join("contoso__widget.py")).unwrap(); + let class_pyi = fs::read_to_string(output.join("contoso__widget.pyi")).unwrap(); + let struct_py = fs::read_to_string(output.join("windows__foundation__point.py")).unwrap(); + assert!(class_py.contains("from .windows__foundation__point import Point")); + assert!(class_pyi.contains("from .windows__foundation__point import Point")); + assert!(!class_py.contains("\nclass Point:")); + assert!(!class_pyi.contains("\nclass Point:")); + assert!(struct_py.contains("\nclass Point:")); + + write_python_package_indexes(&output, std::slice::from_ref(&class), &[], &[], true, false) + .unwrap(); + let point_facade = fs::read_to_string(output.join("windows/foundation/point.py")).unwrap(); + assert!(point_facade.contains("Point.__module__ = __name__")); + assert!(point_facade.contains("Point_TYPE")); + assert!(point_facade.contains("pack_point")); + assert!(point_facade.contains("unpack_point")); + let point_stub = fs::read_to_string(output.join("windows/foundation/point.pyi")).unwrap(); + assert!(point_stub.contains("Point_TYPE as Point_TYPE")); + assert!(point_stub.contains("pack_point as pack_point")); + assert!(point_stub.contains("unpack_point as unpack_point")); + let foundation_index = + fs::read_to_string(output.join("windows/foundation/__init__.py")).unwrap(); + assert!(foundation_index.contains("def __getattr__(name):")); + assert!(!foundation_index.contains("from .point import")); + assert!(foundation_index.contains("\"Point\": (\".point\", \"Point\")")); + assert!(!foundation_index.contains("Point_TYPE")); + let root_index = fs::read_to_string(output.join("__init__.py")).unwrap(); + assert!(root_index.contains("\"Point\": (\".windows.foundation.point\", \"Point\")")); + assert!(root_index.contains("\"Widget\": (\".contoso.widget\", \"Widget\")")); + assert!(!root_index.contains("windows__foundation__point")); + assert!( + !fs::read_to_string(output.join("contoso/widget.py")) + .unwrap() + .contains("Point") + ); + fs::remove_dir_all(output).unwrap(); + } + + #[test] + fn python_generation_rejects_consumer_struct_symbol_collisions() { + let point = |namespace: &str| TypeMeta::Struct { + namespace: namespace.into(), + name: "Point".into(), + fields: vec![dynwinrt_codegen::types::FieldMeta { + name: "Value".into(), + typ: TypeMeta::I32, + }], + }; + let class = meta::ClassMeta { + name: "Widget".into(), + namespace: "Contoso".into(), + full_name: "Contoso.Widget".into(), + default_interface: Some(meta::InterfaceMeta { + name: "IWidget".into(), + namespace: "Contoso".into(), + iid: "11111111-1111-1111-1111-111111111111".into(), + methods: vec![meta::MethodMeta { + name: "Transform".into(), + raw_name: "Transform".into(), + params: vec![ + meta::ParamMeta { + name: "source".into(), + typ: point("Contoso.Geometry"), + direction: meta::ParamDirection::In, + }, + meta::ParamMeta { + name: "target".into(), + typ: point("Fabrikam.Geometry"), + direction: meta::ParamDirection::In, + }, + ], + ..Default::default() + }], + ..Default::default() + }), + ..Default::default() + }; + + let error = generate_for_types( + "", + &test_directory("python-struct-symbol-collision"), + vec![class], + Vec::new(), + Vec::new(), + true, + "py", + true, + &DocTable::default(), + &[], + ) + .unwrap_err(); + assert!(error.contains("Contoso.Widget")); + assert!(error.contains("Contoso.Geometry.Point")); + assert!(error.contains("Fabrikam.Geometry.Point")); + assert!(error.contains("_pack_point")); + } + + #[test] + fn incremental_python_generation_reuses_existing_module_layout() { + let existing = [python::PythonTypeIdentity { + namespace: "Windows.Foundation.Collections".into(), + name: "IIterable_IKeyValuePair_Object_Object".into(), + }]; + let _layout = install_python_generation_layout(&[], &[], &[], &[], &existing).unwrap(); + + assert_eq!( + python::to_snake_case_filename("IIterable_IKeyValuePair_Object_Object"), + "windows__foundation__collections__i_iterable_i_key_value_pair_object_object" + ); + } + + #[test] + fn python_generation_layout_and_inventory_include_shared_interfaces() { + let output = test_directory("python-shared-interface-layout"); + fs::create_dir_all(&output).unwrap(); + let shared = [meta::InterfaceMeta { + namespace: "Windows.Foundation.Collections".into(), + name: "IIterable_IKeyValuePair_Object_Object".into(), + ..Default::default() + }]; + + { + let _layout = install_python_generation_layout(&[], &[], &[], &shared, &[]).unwrap(); + assert_eq!( + python::to_snake_case_filename("IIterable_IKeyValuePair_Object_Object"), + "windows__foundation__collections__i_iterable_i_key_value_pair_object_object" + ); + } + + write_python_type_inventory(&output, &[]).unwrap(); + record_python_supplemental_types(&output, &shared).unwrap(); + let inventory = read_python_type_inventory(&output).unwrap(); + assert!(inventory.iter().any(|typ| typ.identity + == python::PythonTypeIdentity { + namespace: shared[0].namespace.clone(), + name: shared[0].name.clone(), + })); + fs::remove_dir_all(output).unwrap(); + } + #[test] fn dropped_python_output_transaction_preserves_existing_output() { let output = test_directory("transaction-drop"); @@ -3706,7 +4116,7 @@ mod tests { } #[test] - fn namespace_stub_facades_explicitly_reexport_all_symbols() { + fn type_facades_keep_abi_symbols_while_public_indexes_export_types_only() { let output = test_directory("stub-reexports"); fs::create_dir_all(&output).unwrap(); let interface = meta::InterfaceMeta { @@ -3738,13 +4148,16 @@ mod tests { let root_runtime = fs::read_to_string(output.join("__init__.py")).unwrap(); assert!(root_runtime.contains("def __getattr__(name):")); assert!( - root_runtime.contains("\"IWidget\": (\".contoso__foundation__i_widget\", \"IWidget\")") + root_runtime.contains("\"IWidget\": (\".contoso.foundation.i_widget\", \"IWidget\")") ); - assert!(!root_runtime.contains("from .contoso__foundation__i_widget import")); + assert!(!root_runtime.contains("contoso__foundation__i_widget")); let root_stub = fs::read_to_string(output.join("__init__.pyi")).unwrap(); - assert!(root_stub.contains( - "from .contoso__foundation__i_widget import IID_IWidget, IWidget as IWidget" - )); + assert!(root_stub.contains("from .contoso.foundation.i_widget import IWidget as IWidget")); + assert!(!root_stub.contains("IID_IWidget")); + let namespace_stub = + fs::read_to_string(output.join("contoso/foundation/__init__.pyi")).unwrap(); + assert!(namespace_stub.contains("from .i_widget import IWidget as IWidget")); + assert!(!namespace_stub.contains("IID_IWidget")); fs::remove_dir_all(output).unwrap(); } diff --git a/tools/dynwinrt-codegen/src/meta.rs b/tools/dynwinrt-codegen/src/meta.rs index a05cc373..f2ab4a20 100644 --- a/tools/dynwinrt-codegen/src/meta.rs +++ b/tools/dynwinrt-codegen/src/meta.rs @@ -1841,7 +1841,7 @@ mod tests { ); assert!(py.contains("def _from_native(cls, obj: DynWinRTValue):")); assert!(py.contains("User cannot be constructed directly")); - assert!(pyi.contains("def __new__(cls, _not_constructible: NoReturn) -> NoReturn: ...")); + assert!(pyi.contains("def __init__(self, _not_constructible: NoReturn) -> None: ...")); } #[test] @@ -1970,7 +1970,7 @@ mod tests { ); assert!(py.contains("AutomationPeer cannot be constructed directly")); assert!(py.contains("def _from_native(cls, obj: DynWinRTValue):")); - assert!(pyi.contains("def __new__(cls, _not_constructible: NoReturn) -> NoReturn: ...")); + assert!(pyi.contains("def __init__(self, _not_constructible: NoReturn) -> None: ...")); } #[test] diff --git a/tools/dynwinrt-codegen/tests/ireference_struct_field_test.rs b/tools/dynwinrt-codegen/tests/ireference_struct_field_test.rs index cde78f4a..c613662e 100644 --- a/tools/dynwinrt-codegen/tests/ireference_struct_field_test.rs +++ b/tools/dynwinrt-codegen/tests/ireference_struct_field_test.rs @@ -311,7 +311,7 @@ fn nested_struct_defaults_and_enum_fields_are_python_native() { "{py}" ); assert!( - py.contains("def __init__(self, mode: 'Mode' = _dynwinrt_enum('mode', 'Mode', 0), inner: Inner | None = None):"), + py.contains("def __init__(self, mode: 'Mode' = _dynwinrt_enum('synthetic__mode', 'Mode', 0), inner: Inner | None = None):"), "{py}" ); assert!(!py.contains("inner: 'Inner' | None"), "{py}"); @@ -320,7 +320,7 @@ fn nested_struct_defaults_and_enum_fields_are_python_native() { "{py}" ); assert!( - py.contains("mode=_dynwinrt_enum('mode', 'Mode', s.get_u32(0))"), + py.contains("mode=_dynwinrt_enum('synthetic__mode', 'Mode', s.get_u32(0))"), "{py}" ); assert!(py.contains("s.set_u32(0, int(v.mode))"), "{py}"); diff --git a/tools/dynwinrt-codegen/tests/python_constructor_boundary_test.rs b/tools/dynwinrt-codegen/tests/python_constructor_boundary_test.rs index 28d67b26..e70d31b0 100644 --- a/tools/dynwinrt-codegen/tests/python_constructor_boundary_test.rs +++ b/tools/dynwinrt-codegen/tests/python_constructor_boundary_test.rs @@ -58,7 +58,8 @@ fn system_returned_class_keeps_only_internal_native_wrapping() { assert!(py.contains("SystemResult cannot be constructed directly")); assert!(!py.contains("self._set_native(type(self).create(")); assert!(!py.contains("_IActivationFactory =")); - assert!(pyi.contains("def __new__(cls, _not_constructible: NoReturn) -> NoReturn: ...")); + assert!(pyi.contains("def __init__(self, _not_constructible: NoReturn) -> None: ...")); + assert!(pyi.contains("def get_current() -> SystemResult | None: ...")); assert!(!pyi.contains("def __init__(self, obj: DynWinRTValue)")); assert!(!pyi.contains("def __init__(self)")); } @@ -185,7 +186,7 @@ fn protected_composition_is_not_public_construction() { let pyi = python_stub::generate_class_stub(&class, &known, &HashSet::new(), &HashSet::new()); assert!(py.contains("SystemResult cannot be constructed directly")); - assert!(pyi.contains("def __new__(cls, _not_constructible: NoReturn) -> NoReturn: ...")); + assert!(pyi.contains("def __init__(self, _not_constructible: NoReturn) -> None: ...")); assert!(!pyi.contains("def create() -> 'SystemResult'")); } @@ -233,7 +234,7 @@ fn unresolved_or_unsupported_constructor_metadata_fails_closed() { let pyi = python_stub::generate_class_stub(class, &known, &HashSet::new(), &HashSet::new()); assert!(py.contains("SystemResult cannot be constructed directly")); assert!(pyi.contains("from typing import NoReturn")); - assert!(pyi.contains("def __new__(cls, _not_constructible: NoReturn) -> NoReturn: ...")); - assert!(!pyi.contains("def __init__(self")); + assert!(pyi.contains("def __init__(self, _not_constructible: NoReturn) -> None: ...")); + assert_eq!(pyi.matches("def __init__(self").count(), 1); } } diff --git a/tools/dynwinrt-codegen/tests/python_numeric_overload_dispatch_test.rs b/tools/dynwinrt-codegen/tests/python_numeric_overload_dispatch_test.rs index 52312e1f..efc16895 100644 --- a/tools/dynwinrt-codegen/tests/python_numeric_overload_dispatch_test.rs +++ b/tools/dynwinrt-codegen/tests/python_numeric_overload_dispatch_test.rs @@ -43,7 +43,7 @@ fn python_numeric_overload_stubs_retain_typing_overload() { let pyi = python_stub::generate_class_stub(&class, &known, &HashSet::new(), &HashSet::new()); - assert!(pyi.contains("from typing import overload")); + assert!(pyi.contains("overload, timedelta")); assert_eq!(pyi.matches(" @overload\n").count(), 2); assert!( pyi.find("def pick(self, value: int)") < pyi.find("def pick(self, value: float)"), diff --git a/tools/dynwinrt-codegen/tests/python_property_name_collision_test.rs b/tools/dynwinrt-codegen/tests/python_property_name_collision_test.rs index 15f9b501..5e972aa9 100644 --- a/tools/dynwinrt-codegen/tests/python_property_name_collision_test.rs +++ b/tools/dynwinrt-codegen/tests/python_property_name_collision_test.rs @@ -44,7 +44,7 @@ fn property_named_property_does_not_shadow_the_decorator() { let py = python::generate_class(&class, &known, &HashSet::new(), &HashSet::new()); let pyi = python_stub::generate_class_stub(&class, &known, &HashSet::new(), &HashSet::new()); - assert!(py.contains("from builtins import property as _property")); + assert!(py.contains("_property, _weakref_ref,")); assert_eq!(py.matches(" @_property\n").count(), 2); assert!(py.contains("def property(self)")); assert!(py.contains("def old_value(self)")); diff --git a/tools/dynwinrt-codegen/tests/snapshot_test.rs b/tools/dynwinrt-codegen/tests/snapshot_test.rs index 5aeab484..e4e6d865 100644 --- a/tools/dynwinrt-codegen/tests/snapshot_test.rs +++ b/tools/dynwinrt-codegen/tests/snapshot_test.rs @@ -211,6 +211,11 @@ fn snapshot_uri_pyi_class() { "Snapshot directory not found: {}", snapshot_dir.display() ); + if std::env::var_os("DYNWINRT_UPDATE_PY_SNAPSHOTS").is_some() { + for (filename, actual) in &generated { + fs::write(snapshot_dir.join(filename), actual).expect("write Python stub snapshot"); + } + } let mut mismatches = Vec::new(); for (filename, actual) in &generated { diff --git a/tools/dynwinrt-codegen/tests/snapshots/data_writer_py/data_writer.py b/tools/dynwinrt-codegen/tests/snapshots/data_writer_py/data_writer.py index 19574457..81cf8112 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/data_writer_py/data_writer.py +++ b/tools/dynwinrt-codegen/tests/snapshots/data_writer_py/data_writer.py @@ -1,69 +1,27 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations -from builtins import property as _property -from contextvars import copy_context as _copy_context -from functools import lru_cache -from importlib import import_module -from collections.abc import ( +from ._runtime import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from typing import TYPE_CHECKING -from uuid import UUID -from weakref import ref as _weakref_ref -from dynwinrt import ( + TYPE_CHECKING, UUID, WinGUID, datetime, timedelta, DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, - DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, -) -from dynwinrt.dynwinrt import ( - _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, - _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, - _dynwinrt_timedelta_to_ticks, _dynwinrt_cache_projected, _dynwinrt_projected_from_native, - _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, + DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, + _property, _weakref_ref, + _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_create_delegate, + _dynwinrt_datetime_to_ticks, _dynwinrt_delegate, _dynwinrt_enum, _dynwinrt_guid, + _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, + _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, + _dynwinrt_cache_projected, _dynwinrt_projected_from_native, + _dynwinrt_symbol, _dynwinrt_track_projected, _dynwinrt_uuid, + _dynwinrt_vector, _dynwinrt_wrap_values, ) - - -@lru_cache(maxsize=None) -def _dynwinrt_symbol(module, name): - return getattr(import_module(f'.{module}', __package__), name) - - -def _dynwinrt_wrap_values(module, name, values): - wrapper = _dynwinrt_symbol(module, name) - wrap = getattr(wrapper, '_from_native', wrapper) - return [None if value.is_null() else wrap(value) for value in values] - - -def _dynwinrt_enum(module, name, value): - enum_type = _dynwinrt_symbol(module, name) - try: - return enum_type(value) - except ValueError: - return value - - -def _dynwinrt_create_delegate(iid, parameter_types, callback): - context = _copy_context() - def invoke(*args): - return context.copy().run(callback, *args) - return DynWinRtDelegate.create(iid, parameter_types, invoke) - -def _dynwinrt_delegate(value, iid, parameter_types): - raw = getattr(value, '_obj', value) - if isinstance(raw, DynWinRTValue): - return raw - if not callable(value): - raise TypeError('delegate value must be callable or a DynWinRTValue') - return _dynwinrt_create_delegate(iid, parameter_types, value).to_value() - from dynwinrt import WinRTAsync, WinRTAsyncWithProgress from dynwinrt.dynwinrt import _DynWinRTAsync, _DynWinRTAsyncWithProgress if TYPE_CHECKING: - from .byte_order import ByteOrder # noqa: F401 - from .i_buffer import IID_IBuffer, IBuffer # noqa: F401 - from .i_output_stream import IID_IOutputStream, IOutputStream # noqa: F401 - from .unicode_encoding import UnicodeEncoding # noqa: F401 + from .windows__storage__streams__byte_order import ByteOrder # noqa: F401 + from .windows__storage__streams__i_buffer import IID_IBuffer, IBuffer # noqa: F401 + from .windows__storage__streams__i_output_stream import IID_IOutputStream, IOutputStream # noqa: F401 + from .windows__storage__streams__unicode_encoding import UnicodeEncoding # noqa: F401 IID_IDataWriter = WinGUID.parse('64b89265-d341-4922-b38a-dd4af8808c4e') IID_IDataWriterFactory = WinGUID.parse('338c67c2-8b84-4c2b-9c50-7b8767847a1f') @@ -147,7 +105,7 @@ def __new__(cls, *args, **kwargs): if _bound is not None: return cls.create_default() _bound = _dynwinrt_bind_overload(('output_stream',), args, kwargs) - if _bound is not None and isinstance(_bound[0], _dynwinrt_symbol('i_output_stream', 'IOutputStream')): + if _bound is not None and isinstance(_bound[0], _dynwinrt_symbol('windows__storage__streams__i_output_stream', 'IOutputStream')): return cls.create_data_writer(_bound[0]) return super().__new__(cls) @@ -173,7 +131,7 @@ def __init__(self, *args, **kwargs): self._set_native(type(self).create_default()._obj) return _bound = _dynwinrt_bind_overload(('output_stream',), args, kwargs) - if _bound is not None and isinstance(_bound[0], _dynwinrt_symbol('i_output_stream', 'IOutputStream')): + if _bound is not None and isinstance(_bound[0], _dynwinrt_symbol('windows__storage__streams__i_output_stream', 'IOutputStream')): self._set_native(type(self).create_data_writer(_bound[0])._obj) return raise TypeError("No matching constructor for DataWriter") @@ -197,11 +155,11 @@ def unstored_buffer_length(self) -> int: @_property def unicode_encoding(self) -> 'UnicodeEncoding': - return _dynwinrt_enum('unicode_encoding', 'UnicodeEncoding', _IDataWriter.method(7).invoke(self._obj, []).to_number()) + return _dynwinrt_enum('windows__storage__streams__unicode_encoding', 'UnicodeEncoding', _IDataWriter.method(7).invoke(self._obj, []).to_number()) @_property def byte_order(self) -> 'ByteOrder': - return _dynwinrt_enum('byte_order', 'ByteOrder', _IDataWriter.method(9).invoke(self._obj, []).to_number()) + return _dynwinrt_enum('windows__storage__streams__byte_order', 'ByteOrder', _IDataWriter.method(9).invoke(self._obj, []).to_number()) def write_byte(self, value: int) -> None: _IDataWriter.method(11).invoke(self._obj, [DynWinRTValue.from_u8(value)]) @@ -264,10 +222,10 @@ def flush_async(self) -> WinRTAsync[bool]: return _dynwinrt_track_projected(_DynWinRTAsync(_IDataWriter.method(30).invoke(self._obj, []), lambda value: value.to_bool()), 'WinRTAsync') def detach_buffer(self) -> IBuffer | None: - return (lambda value: None if value.is_null() else _dynwinrt_symbol('i_buffer', 'IBuffer')(value))(_IDataWriter.method(31).invoke(self._obj, [])) + return (lambda value: None if value.is_null() else _dynwinrt_symbol('windows__storage__streams__i_buffer', 'IBuffer')(value))(_IDataWriter.method(31).invoke(self._obj, [])) def detach_stream(self) -> IOutputStream | None: - return (lambda value: None if value.is_null() else _dynwinrt_symbol('i_output_stream', 'IOutputStream')(value))(_IDataWriter.method(32).invoke(self._obj, [])) + return (lambda value: None if value.is_null() else _dynwinrt_symbol('windows__storage__streams__i_output_stream', 'IOutputStream')(value))(_IDataWriter.method(32).invoke(self._obj, [])) @unicode_encoding.setter def unicode_encoding(self, value: 'UnicodeEncoding'): diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/__init__.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/__init__.py index cbbec5c3..a64d55f0 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/__init__.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/__init__.py @@ -1,7 +1,7 @@ # Generated by dynwinrt-codegen — do not edit -from .uri import Uri # noqa: F401 -from .www_form_url_decoder import WwwFormUrlDecoder # noqa: F401 -from .i_iterator_i_www_form_url_decoder_entry import IID_IIterator_IWwwFormUrlDecoderEntry, IIterator_IWwwFormUrlDecoderEntry # noqa: F401 -from .i_stringable import IID_IStringable, IStringable # noqa: F401 -from .i_uri_runtime_class_with_absolute_canonical_uri import IID_IUriRuntimeClassWithAbsoluteCanonicalUri, IUriRuntimeClassWithAbsoluteCanonicalUri # noqa: F401 -from .i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 +from .windows__foundation__uri import Uri # noqa: F401 +from .windows__foundation__www_form_url_decoder import WwwFormUrlDecoder # noqa: F401 +from .windows__foundation__collections__i_iterator_i_www_form_url_decoder_entry import IID_IIterator_IWwwFormUrlDecoderEntry, IIterator_IWwwFormUrlDecoderEntry # noqa: F401 +from .windows__foundation__i_stringable import IID_IStringable, IStringable # noqa: F401 +from .windows__foundation__i_uri_runtime_class_with_absolute_canonical_uri import IID_IUriRuntimeClassWithAbsoluteCanonicalUri, IUriRuntimeClassWithAbsoluteCanonicalUri # noqa: F401 +from .windows__foundation__i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_iterator_i_www_form_url_decoder_entry.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_iterator_i_www_form_url_decoder_entry.py index fb643560..f009ca8c 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_iterator_i_www_form_url_decoder_entry.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_iterator_i_www_form_url_decoder_entry.py @@ -1,65 +1,23 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations -from builtins import property as _property -from contextvars import copy_context as _copy_context -from functools import lru_cache -from importlib import import_module -from collections.abc import ( +from ._runtime import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from typing import TYPE_CHECKING -from uuid import UUID -from weakref import ref as _weakref_ref -from dynwinrt import ( + TYPE_CHECKING, UUID, WinGUID, datetime, timedelta, DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, - DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, -) -from dynwinrt.dynwinrt import ( - _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, - _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, - _dynwinrt_timedelta_to_ticks, _dynwinrt_cache_projected, _dynwinrt_projected_from_native, - _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, + DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, + _property, _weakref_ref, + _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_create_delegate, + _dynwinrt_datetime_to_ticks, _dynwinrt_delegate, _dynwinrt_enum, _dynwinrt_guid, + _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, + _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, + _dynwinrt_cache_projected, _dynwinrt_projected_from_native, + _dynwinrt_symbol, _dynwinrt_track_projected, _dynwinrt_uuid, + _dynwinrt_vector, _dynwinrt_wrap_values, ) - - -@lru_cache(maxsize=None) -def _dynwinrt_symbol(module, name): - return getattr(import_module(f'.{module}', __package__), name) - - -def _dynwinrt_wrap_values(module, name, values): - wrapper = _dynwinrt_symbol(module, name) - wrap = getattr(wrapper, '_from_native', wrapper) - return [None if value.is_null() else wrap(value) for value in values] - - -def _dynwinrt_enum(module, name, value): - enum_type = _dynwinrt_symbol(module, name) - try: - return enum_type(value) - except ValueError: - return value - - -def _dynwinrt_create_delegate(iid, parameter_types, callback): - context = _copy_context() - def invoke(*args): - return context.copy().run(callback, *args) - return DynWinRtDelegate.create(iid, parameter_types, invoke) - -def _dynwinrt_delegate(value, iid, parameter_types): - raw = getattr(value, '_obj', value) - if isinstance(raw, DynWinRTValue): - return raw - if not callable(value): - raise TypeError('delegate value must be callable or a DynWinRTValue') - return _dynwinrt_create_delegate(iid, parameter_types, value).to_value() - from dynwinrt.dynwinrt import _WinRTIteratorMixin if TYPE_CHECKING: - from .i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 + from .windows__foundation__i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 IID_IIterator_IWwwFormUrlDecoderEntry = DynWinRTType.parameterized(WinGUID.parse('6a79e863-4300-459a-9966-cbb660963ee1'), [DynWinRTType.interface(WinGUID.parse('125e7431-f678-4e8e-b670-20a9b06c512d'))]).iid() @@ -99,7 +57,7 @@ def from_value(obj: DynWinRTValue) -> 'IIterator_IWwwFormUrlDecoderEntry': @_property def current(self) -> IWwwFormUrlDecoderEntry | None: - return (lambda value: None if value.is_null() else _dynwinrt_symbol('i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry')(value))(_IIterator_IWwwFormUrlDecoderEntry.method(6).invoke(self._obj, [])) + return (lambda value: None if value.is_null() else _dynwinrt_symbol('windows__foundation__i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry')(value))(_IIterator_IWwwFormUrlDecoderEntry.method(6).invoke(self._obj, [])) @_property def has_current(self) -> bool: @@ -110,4 +68,4 @@ def move_next(self) -> bool: def get_many(self, items: DynWinRTArray | Sequence['IWwwFormUrlDecoderEntry']) -> list[IWwwFormUrlDecoderEntry | None]: _results = _IIterator_IWwwFormUrlDecoderEntry.method(9).invoke_all(self._obj, [_dynwinrt_array(items, lambda item: getattr(item, '_obj', item), DynWinRTType.interface(WinGUID.parse('125e7431-f678-4e8e-b670-20a9b06c512d')), False)]) - return _dynwinrt_wrap_values('i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry', _results[0].as_array().to_values())[:_results[1].to_number()] + return _dynwinrt_wrap_values('windows__foundation__i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry', _results[0].as_array().to_values())[:_results[1].to_number()] diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_stringable.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_stringable.py index abde8bf7..b3dd47fd 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_stringable.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_stringable.py @@ -1,62 +1,20 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations -from builtins import property as _property -from contextvars import copy_context as _copy_context -from functools import lru_cache -from importlib import import_module -from collections.abc import ( +from ._runtime import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from typing import TYPE_CHECKING -from uuid import UUID -from weakref import ref as _weakref_ref -from dynwinrt import ( + TYPE_CHECKING, UUID, WinGUID, datetime, timedelta, DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, - DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, -) -from dynwinrt.dynwinrt import ( - _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, - _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, - _dynwinrt_timedelta_to_ticks, _dynwinrt_cache_projected, _dynwinrt_projected_from_native, - _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, + DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, + _property, _weakref_ref, + _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_create_delegate, + _dynwinrt_datetime_to_ticks, _dynwinrt_delegate, _dynwinrt_enum, _dynwinrt_guid, + _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, + _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, + _dynwinrt_cache_projected, _dynwinrt_projected_from_native, + _dynwinrt_symbol, _dynwinrt_track_projected, _dynwinrt_uuid, + _dynwinrt_vector, _dynwinrt_wrap_values, ) - -@lru_cache(maxsize=None) -def _dynwinrt_symbol(module, name): - return getattr(import_module(f'.{module}', __package__), name) - - -def _dynwinrt_wrap_values(module, name, values): - wrapper = _dynwinrt_symbol(module, name) - wrap = getattr(wrapper, '_from_native', wrapper) - return [None if value.is_null() else wrap(value) for value in values] - - -def _dynwinrt_enum(module, name, value): - enum_type = _dynwinrt_symbol(module, name) - try: - return enum_type(value) - except ValueError: - return value - - -def _dynwinrt_create_delegate(iid, parameter_types, callback): - context = _copy_context() - def invoke(*args): - return context.copy().run(callback, *args) - return DynWinRtDelegate.create(iid, parameter_types, invoke) - -def _dynwinrt_delegate(value, iid, parameter_types): - raw = getattr(value, '_obj', value) - if isinstance(raw, DynWinRTValue): - return raw - if not callable(value): - raise TypeError('delegate value must be callable or a DynWinRTValue') - return _dynwinrt_create_delegate(iid, parameter_types, value).to_value() - - IID_IStringable = WinGUID.parse('96369f54-8eb6-48f0-abce-c1b211e627c3') _IStringable = DynWinRTType.register_interface( diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_uri_runtime_class_with_absolute_canonical_uri.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_uri_runtime_class_with_absolute_canonical_uri.py index 2e7358bb..f2d7ff85 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_uri_runtime_class_with_absolute_canonical_uri.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_uri_runtime_class_with_absolute_canonical_uri.py @@ -1,62 +1,20 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations -from builtins import property as _property -from contextvars import copy_context as _copy_context -from functools import lru_cache -from importlib import import_module -from collections.abc import ( +from ._runtime import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from typing import TYPE_CHECKING -from uuid import UUID -from weakref import ref as _weakref_ref -from dynwinrt import ( + TYPE_CHECKING, UUID, WinGUID, datetime, timedelta, DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, - DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, -) -from dynwinrt.dynwinrt import ( - _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, - _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, - _dynwinrt_timedelta_to_ticks, _dynwinrt_cache_projected, _dynwinrt_projected_from_native, - _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, + DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, + _property, _weakref_ref, + _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_create_delegate, + _dynwinrt_datetime_to_ticks, _dynwinrt_delegate, _dynwinrt_enum, _dynwinrt_guid, + _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, + _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, + _dynwinrt_cache_projected, _dynwinrt_projected_from_native, + _dynwinrt_symbol, _dynwinrt_track_projected, _dynwinrt_uuid, + _dynwinrt_vector, _dynwinrt_wrap_values, ) - -@lru_cache(maxsize=None) -def _dynwinrt_symbol(module, name): - return getattr(import_module(f'.{module}', __package__), name) - - -def _dynwinrt_wrap_values(module, name, values): - wrapper = _dynwinrt_symbol(module, name) - wrap = getattr(wrapper, '_from_native', wrapper) - return [None if value.is_null() else wrap(value) for value in values] - - -def _dynwinrt_enum(module, name, value): - enum_type = _dynwinrt_symbol(module, name) - try: - return enum_type(value) - except ValueError: - return value - - -def _dynwinrt_create_delegate(iid, parameter_types, callback): - context = _copy_context() - def invoke(*args): - return context.copy().run(callback, *args) - return DynWinRtDelegate.create(iid, parameter_types, invoke) - -def _dynwinrt_delegate(value, iid, parameter_types): - raw = getattr(value, '_obj', value) - if isinstance(raw, DynWinRTValue): - return raw - if not callable(value): - raise TypeError('delegate value must be callable or a DynWinRTValue') - return _dynwinrt_create_delegate(iid, parameter_types, value).to_value() - - IID_IUriRuntimeClassWithAbsoluteCanonicalUri = WinGUID.parse('758d9661-221c-480f-a339-50656673f46f') _IUriRuntimeClassWithAbsoluteCanonicalUri = DynWinRTType.register_interface( diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_www_form_url_decoder_entry.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_www_form_url_decoder_entry.py index e805b391..67b929cb 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_www_form_url_decoder_entry.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/i_www_form_url_decoder_entry.py @@ -1,62 +1,20 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations -from builtins import property as _property -from contextvars import copy_context as _copy_context -from functools import lru_cache -from importlib import import_module -from collections.abc import ( +from ._runtime import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from typing import TYPE_CHECKING -from uuid import UUID -from weakref import ref as _weakref_ref -from dynwinrt import ( + TYPE_CHECKING, UUID, WinGUID, datetime, timedelta, DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, - DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, -) -from dynwinrt.dynwinrt import ( - _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, - _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, - _dynwinrt_timedelta_to_ticks, _dynwinrt_cache_projected, _dynwinrt_projected_from_native, - _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, + DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, + _property, _weakref_ref, + _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_create_delegate, + _dynwinrt_datetime_to_ticks, _dynwinrt_delegate, _dynwinrt_enum, _dynwinrt_guid, + _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, + _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, + _dynwinrt_cache_projected, _dynwinrt_projected_from_native, + _dynwinrt_symbol, _dynwinrt_track_projected, _dynwinrt_uuid, + _dynwinrt_vector, _dynwinrt_wrap_values, ) - -@lru_cache(maxsize=None) -def _dynwinrt_symbol(module, name): - return getattr(import_module(f'.{module}', __package__), name) - - -def _dynwinrt_wrap_values(module, name, values): - wrapper = _dynwinrt_symbol(module, name) - wrap = getattr(wrapper, '_from_native', wrapper) - return [None if value.is_null() else wrap(value) for value in values] - - -def _dynwinrt_enum(module, name, value): - enum_type = _dynwinrt_symbol(module, name) - try: - return enum_type(value) - except ValueError: - return value - - -def _dynwinrt_create_delegate(iid, parameter_types, callback): - context = _copy_context() - def invoke(*args): - return context.copy().run(callback, *args) - return DynWinRtDelegate.create(iid, parameter_types, invoke) - -def _dynwinrt_delegate(value, iid, parameter_types): - raw = getattr(value, '_obj', value) - if isinstance(raw, DynWinRTValue): - return raw - if not callable(value): - raise TypeError('delegate value must be callable or a DynWinRTValue') - return _dynwinrt_create_delegate(iid, parameter_types, value).to_value() - - IID_IWwwFormUrlDecoderEntry = WinGUID.parse('125e7431-f678-4e8e-b670-20a9b06c512d') _IWwwFormUrlDecoderEntry = DynWinRTType.register_interface( diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/uri.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/uri.py index 197fc071..47b8d7ec 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/uri.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/uri.py @@ -1,64 +1,22 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations -from builtins import property as _property -from contextvars import copy_context as _copy_context -from functools import lru_cache -from importlib import import_module -from collections.abc import ( +from ._runtime import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from typing import TYPE_CHECKING -from uuid import UUID -from weakref import ref as _weakref_ref -from dynwinrt import ( + TYPE_CHECKING, UUID, WinGUID, datetime, timedelta, DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, - DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, -) -from dynwinrt.dynwinrt import ( - _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, - _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, - _dynwinrt_timedelta_to_ticks, _dynwinrt_cache_projected, _dynwinrt_projected_from_native, - _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, + DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, + _property, _weakref_ref, + _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_create_delegate, + _dynwinrt_datetime_to_ticks, _dynwinrt_delegate, _dynwinrt_enum, _dynwinrt_guid, + _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, + _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, + _dynwinrt_cache_projected, _dynwinrt_projected_from_native, + _dynwinrt_symbol, _dynwinrt_track_projected, _dynwinrt_uuid, + _dynwinrt_vector, _dynwinrt_wrap_values, ) - -@lru_cache(maxsize=None) -def _dynwinrt_symbol(module, name): - return getattr(import_module(f'.{module}', __package__), name) - - -def _dynwinrt_wrap_values(module, name, values): - wrapper = _dynwinrt_symbol(module, name) - wrap = getattr(wrapper, '_from_native', wrapper) - return [None if value.is_null() else wrap(value) for value in values] - - -def _dynwinrt_enum(module, name, value): - enum_type = _dynwinrt_symbol(module, name) - try: - return enum_type(value) - except ValueError: - return value - - -def _dynwinrt_create_delegate(iid, parameter_types, callback): - context = _copy_context() - def invoke(*args): - return context.copy().run(callback, *args) - return DynWinRtDelegate.create(iid, parameter_types, invoke) - -def _dynwinrt_delegate(value, iid, parameter_types): - raw = getattr(value, '_obj', value) - if isinstance(raw, DynWinRTValue): - return raw - if not callable(value): - raise TypeError('delegate value must be callable or a DynWinRTValue') - return _dynwinrt_create_delegate(iid, parameter_types, value).to_value() - - if TYPE_CHECKING: - from .www_form_url_decoder import WwwFormUrlDecoder # noqa: F401 + from .windows__foundation__www_form_url_decoder import WwwFormUrlDecoder # noqa: F401 IID_IUriRuntimeClass = WinGUID.parse('9e365e57-48b2-4160-956f-c7385120bbfc') IID_IUriRuntimeClassFactory = WinGUID.parse('44a9796f-723e-4fdf-a218-033e75b0c084') @@ -209,7 +167,7 @@ def query(self) -> str: @_property def query_parsed(self) -> WwwFormUrlDecoder | None: - return (lambda value: None if value.is_null() else _dynwinrt_symbol('www_form_url_decoder', 'WwwFormUrlDecoder')._from_native(value))(_IUriRuntimeClass.method(15).invoke(self._obj, [])) + return (lambda value: None if value.is_null() else _dynwinrt_symbol('windows__foundation__www_form_url_decoder', 'WwwFormUrlDecoder')._from_native(value))(_IUriRuntimeClass.method(15).invoke(self._obj, [])) @_property def raw_uri(self) -> str: @@ -235,7 +193,7 @@ def equals(self, p_uri: 'Uri') -> bool: return _IUriRuntimeClass.method(21).invoke(self._obj, [getattr(p_uri, '_obj', p_uri).cast(IID_ARG_Windows_Foundation_Uri)]).to_bool() def combine_uri(self, relative_uri: str) -> Uri | None: - return (lambda value: None if value.is_null() else _dynwinrt_symbol('uri', 'Uri')._from_native(value))(_IUriRuntimeClass.method(22).invoke(self._obj, [DynWinRTValue.from_hstring(relative_uri)])) + return (lambda value: None if value.is_null() else _dynwinrt_symbol('windows__foundation__uri', 'Uri')._from_native(value))(_IUriRuntimeClass.method(22).invoke(self._obj, [DynWinRTValue.from_hstring(relative_uri)])) @_property def absolute_canonical_uri(self) -> str: diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_py/www_form_url_decoder.py b/tools/dynwinrt-codegen/tests/snapshots/uri_py/www_form_url_decoder.py index 4435d5fe..dae6b2d1 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_py/www_form_url_decoder.py +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_py/www_form_url_decoder.py @@ -1,66 +1,24 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations -from builtins import property as _property -from contextvars import copy_context as _copy_context -from functools import lru_cache -from importlib import import_module -from collections.abc import ( +from ._runtime import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from typing import TYPE_CHECKING -from uuid import UUID -from weakref import ref as _weakref_ref -from dynwinrt import ( + TYPE_CHECKING, UUID, WinGUID, datetime, timedelta, DynWinRTType, DynWinRTMethodSig, DynWinRTValue, DynWinRTArray, - DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, WinGUID, -) -from dynwinrt.dynwinrt import ( - _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_datetime_to_ticks, _dynwinrt_guid, - _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, _dynwinrt_ticks_to_timedelta, - _dynwinrt_timedelta_to_ticks, _dynwinrt_cache_projected, _dynwinrt_projected_from_native, - _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, + DynWinRTStruct, DynWinRtDelegate, DynWinRTOverrideInterface, + _property, _weakref_ref, + _dynwinrt_array, _dynwinrt_bind_overload, _dynwinrt_create_delegate, + _dynwinrt_datetime_to_ticks, _dynwinrt_delegate, _dynwinrt_enum, _dynwinrt_guid, + _dynwinrt_map, _dynwinrt_new_vector, _dynwinrt_ticks_to_datetime, + _dynwinrt_ticks_to_timedelta, _dynwinrt_timedelta_to_ticks, + _dynwinrt_cache_projected, _dynwinrt_projected_from_native, + _dynwinrt_symbol, _dynwinrt_track_projected, _dynwinrt_uuid, + _dynwinrt_vector, _dynwinrt_wrap_values, ) - - -@lru_cache(maxsize=None) -def _dynwinrt_symbol(module, name): - return getattr(import_module(f'.{module}', __package__), name) - - -def _dynwinrt_wrap_values(module, name, values): - wrapper = _dynwinrt_symbol(module, name) - wrap = getattr(wrapper, '_from_native', wrapper) - return [None if value.is_null() else wrap(value) for value in values] - - -def _dynwinrt_enum(module, name, value): - enum_type = _dynwinrt_symbol(module, name) - try: - return enum_type(value) - except ValueError: - return value - - -def _dynwinrt_create_delegate(iid, parameter_types, callback): - context = _copy_context() - def invoke(*args): - return context.copy().run(callback, *args) - return DynWinRtDelegate.create(iid, parameter_types, invoke) - -def _dynwinrt_delegate(value, iid, parameter_types): - raw = getattr(value, '_obj', value) - if isinstance(raw, DynWinRTValue): - return raw - if not callable(value): - raise TypeError('delegate value must be callable or a DynWinRTValue') - return _dynwinrt_create_delegate(iid, parameter_types, value).to_value() - from dynwinrt.dynwinrt import _WinRTIterableMixin, _WinRTSequenceMixin if TYPE_CHECKING: from .i_iterator_i_www_form_url_decoder_entry import IIterator_IWwwFormUrlDecoderEntry # noqa: F401 - from .i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 + from .windows__foundation__i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 IID_IWwwFormUrlDecoderRuntimeClass = WinGUID.parse('d45a0451-f225-4542-9296-0e1df5d254df') IID_IWwwFormUrlDecoderRuntimeClassFactory = WinGUID.parse('5b8c6b3d-24ae-41b5-a1bf-f0c3d544845b') @@ -137,7 +95,7 @@ def size(self) -> int: return _IVectorView_IWwwFormUrlDecoderEntry.method(7).invoke(self._collection_obj, []).to_u32() def get_at(self, index: int) -> IWwwFormUrlDecoderEntry | None: - return (lambda value: None if value.is_null() else _dynwinrt_symbol('i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry')(value))(_IVectorView_IWwwFormUrlDecoderEntry.method(6).invoke(self._collection_obj, [DynWinRTValue.from_u32(index)])) + return (lambda value: None if value.is_null() else _dynwinrt_symbol('windows__foundation__i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry')(value))(_IVectorView_IWwwFormUrlDecoderEntry.method(6).invoke(self._collection_obj, [DynWinRTValue.from_u32(index)])) def index_of(self, value: 'IWwwFormUrlDecoderEntry') -> tuple[int, bool]: _results = _IVectorView_IWwwFormUrlDecoderEntry.method(8).invoke_all(self._collection_obj, [getattr(value, '_obj', value)]) @@ -145,7 +103,7 @@ def index_of(self, value: 'IWwwFormUrlDecoderEntry') -> tuple[int, bool]: def get_many(self, start_index: int, items: DynWinRTArray | Sequence['IWwwFormUrlDecoderEntry']) -> list[IWwwFormUrlDecoderEntry | None]: _results = _IVectorView_IWwwFormUrlDecoderEntry.method(9).invoke_all(self._collection_obj, [DynWinRTValue.from_u32(start_index), _dynwinrt_array(items, lambda item: getattr(item, '_obj', item), DynWinRTType.interface(WinGUID.parse('125e7431-f678-4e8e-b670-20a9b06c512d')), False)]) - return _dynwinrt_wrap_values('i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry', _results[0].as_array().to_values())[:_results[1].to_number()] + return _dynwinrt_wrap_values('windows__foundation__i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry', _results[0].as_array().to_values())[:_results[1].to_number()] def first(self) -> Iterator[IWwwFormUrlDecoderEntry | None] | None: return (lambda value: None if value.is_null() else _dynwinrt_symbol('i_iterator_i_www_form_url_decoder_entry', 'IIterator_IWwwFormUrlDecoderEntry')(value))(_IIterable_IWwwFormUrlDecoderEntry.method(6).invoke(self._obj.cast(IID_IIterable_IWwwFormUrlDecoderEntry), [])) @@ -184,7 +142,7 @@ def size(self) -> int: return _IVectorView_IWwwFormUrlDecoderEntry.method(7).invoke(self._obj, []).to_u32() def get_at(self, index: int) -> IWwwFormUrlDecoderEntry | None: - return (lambda value: None if value.is_null() else _dynwinrt_symbol('i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry')(value))(_IVectorView_IWwwFormUrlDecoderEntry.method(6).invoke(self._obj, [DynWinRTValue.from_u32(index)])) + return (lambda value: None if value.is_null() else _dynwinrt_symbol('windows__foundation__i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry')(value))(_IVectorView_IWwwFormUrlDecoderEntry.method(6).invoke(self._obj, [DynWinRTValue.from_u32(index)])) def index_of(self, value: 'IWwwFormUrlDecoderEntry') -> tuple[int, bool]: _results = _IVectorView_IWwwFormUrlDecoderEntry.method(8).invoke_all(self._obj, [getattr(value, '_obj', value)]) @@ -192,7 +150,7 @@ def index_of(self, value: 'IWwwFormUrlDecoderEntry') -> tuple[int, bool]: def get_many(self, start_index: int, items: DynWinRTArray | Sequence['IWwwFormUrlDecoderEntry']) -> list[IWwwFormUrlDecoderEntry | None]: _results = _IVectorView_IWwwFormUrlDecoderEntry.method(9).invoke_all(self._obj, [DynWinRTValue.from_u32(start_index), _dynwinrt_array(items, lambda item: getattr(item, '_obj', item), DynWinRTType.interface(WinGUID.parse('125e7431-f678-4e8e-b670-20a9b06c512d')), False)]) - return _dynwinrt_wrap_values('i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry', _results[0].as_array().to_values())[:_results[1].to_number()] + return _dynwinrt_wrap_values('windows__foundation__i_www_form_url_decoder_entry', 'IWwwFormUrlDecoderEntry', _results[0].as_array().to_values())[:_results[1].to_number()] class IIterable_IWwwFormUrlDecoderEntry(_WinRTIterableMixin): diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/__init__.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/__init__.pyi index 7f3ce23a..af3ad47c 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/__init__.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/__init__.pyi @@ -1,7 +1,7 @@ # Generated by dynwinrt-codegen — do not edit -from .uri import Uri as Uri -from .www_form_url_decoder import WwwFormUrlDecoder as WwwFormUrlDecoder -from .i_iterator_i_www_form_url_decoder_entry import IID_IIterator_IWwwFormUrlDecoderEntry, IIterator_IWwwFormUrlDecoderEntry as IIterator_IWwwFormUrlDecoderEntry -from .i_stringable import IID_IStringable, IStringable as IStringable -from .i_uri_runtime_class_with_absolute_canonical_uri import IID_IUriRuntimeClassWithAbsoluteCanonicalUri, IUriRuntimeClassWithAbsoluteCanonicalUri as IUriRuntimeClassWithAbsoluteCanonicalUri -from .i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry as IWwwFormUrlDecoderEntry +from .windows__foundation__uri import Uri as Uri +from .windows__foundation__www_form_url_decoder import WwwFormUrlDecoder as WwwFormUrlDecoder +from .windows__foundation__collections__i_iterator_i_www_form_url_decoder_entry import IID_IIterator_IWwwFormUrlDecoderEntry, IIterator_IWwwFormUrlDecoderEntry as IIterator_IWwwFormUrlDecoderEntry +from .windows__foundation__i_stringable import IID_IStringable, IStringable as IStringable +from .windows__foundation__i_uri_runtime_class_with_absolute_canonical_uri import IID_IUriRuntimeClassWithAbsoluteCanonicalUri, IUriRuntimeClassWithAbsoluteCanonicalUri as IUriRuntimeClassWithAbsoluteCanonicalUri +from .windows__foundation__i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry as IWwwFormUrlDecoderEntry diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_iterator_i_www_form_url_decoder_entry.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_iterator_i_www_form_url_decoder_entry.pyi index f3ebdb17..381f0a1e 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_iterator_i_www_form_url_decoder_entry.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_iterator_i_www_form_url_decoder_entry.pyi @@ -1,17 +1,13 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations import builtins -from collections.abc import ( +from ._typing import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from uuid import UUID -from typing import overload -from dynwinrt import ( - DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, + UUID, WinGUID, datetime, overload, timedelta, + DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, ) -from .i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 +from .windows__foundation__i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 IID_IIterator_IWwwFormUrlDecoderEntry: WinGUID diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_stringable.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_stringable.pyi index a34159e9..572d154b 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_stringable.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_stringable.pyi @@ -1,14 +1,10 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations import builtins -from collections.abc import ( +from ._typing import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from uuid import UUID -from typing import overload -from dynwinrt import ( - DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, + UUID, WinGUID, datetime, overload, timedelta, + DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, ) diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_uri_runtime_class_with_absolute_canonical_uri.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_uri_runtime_class_with_absolute_canonical_uri.pyi index 107ff32c..e4fc3c69 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_uri_runtime_class_with_absolute_canonical_uri.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_uri_runtime_class_with_absolute_canonical_uri.pyi @@ -1,14 +1,10 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations import builtins -from collections.abc import ( +from ._typing import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from uuid import UUID -from typing import overload -from dynwinrt import ( - DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, + UUID, WinGUID, datetime, overload, timedelta, + DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, ) diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_www_form_url_decoder_entry.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_www_form_url_decoder_entry.pyi index 36398607..555aeaf1 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_www_form_url_decoder_entry.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/i_www_form_url_decoder_entry.pyi @@ -1,14 +1,10 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations import builtins -from collections.abc import ( +from ._typing import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from uuid import UUID -from typing import overload -from dynwinrt import ( - DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, + UUID, WinGUID, datetime, overload, timedelta, + DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, ) diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi index ac4ddc7c..39c50cae 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi @@ -1,18 +1,14 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations import builtins -from collections.abc import ( +from ._typing import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from uuid import UUID -from typing import overload -from dynwinrt import ( - DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, + UUID, WinGUID, datetime, overload, timedelta, + DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, ) from typing import Type, TypeVar -from .www_form_url_decoder import WwwFormUrlDecoder # noqa: F401 +from .windows__foundation__www_form_url_decoder import WwwFormUrlDecoder # noqa: F401 _InterfaceT = TypeVar('_InterfaceT') diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/www_form_url_decoder.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/www_form_url_decoder.pyi index b79bed46..876f14aa 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/www_form_url_decoder.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/www_form_url_decoder.pyi @@ -1,19 +1,15 @@ # Generated by dynwinrt-codegen — do not edit from __future__ import annotations import builtins -from collections.abc import ( +from ._typing import ( Callable, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Sequence, -) -from datetime import datetime, timedelta -from uuid import UUID -from typing import overload -from dynwinrt import ( - DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, WinGUID, + UUID, WinGUID, datetime, overload, timedelta, + DynWinRTType, DynWinRTValue, DynWinRTArray, DynWinRTStruct, DynWinRtDelegate, ) from typing import Type, TypeVar from .i_iterator_i_www_form_url_decoder_entry import IIterator_IWwwFormUrlDecoderEntry # noqa: F401 -from .i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 +from .windows__foundation__i_www_form_url_decoder_entry import IID_IWwwFormUrlDecoderEntry, IWwwFormUrlDecoderEntry # noqa: F401 _InterfaceT = TypeVar('_InterfaceT')