diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4b4e9abd..22507396 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -552,6 +552,23 @@ jobs: --test implementation_naming_test ` --test python_overload_legacy_compat_test ` --test python_released_implementation_test + - name: Test generated nullable collection values + shell: pwsh + run: | + $env:DYNWINRT_TEST_PYTHON = (Resolve-Path .\bindings\py\.venv\Scripts\python.exe).Path + $env:DYNWINRT_REQUIRE_IMPLEMENTATION_RUNTIME = '1' + $env:DYNWINRT_REQUIRE_MYPY = '1' + cargo test -p dynwinrt-codegen --test python_consumer_typing_test ` + mutable_collection_mutators_accept_none -- --exact + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + cargo test -p dynwinrt-codegen --test python_consumer_typing_test ` + reference_array_results_preserve_null_elements -- --exact + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + cargo test -p dynwinrt-codegen --test python_collection_helper_collision_test ` + collection_helper_aliases_execute_for_packaged_and_standalone_outputs -- --exact + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + cargo test -p dynwinrt-codegen --test python_delegate_callback_test ` + nested_delegate_callback_argument_preserves_native_null -- --exact --nocapture - name: Run E2E tests run: .\tests\e2e\e2e_test.ps1 -SkipBuild -Codegen $env:DYNWINRT_CODEGEN # This optional-SDK behavioral smoke is separate from generated coverage diff --git a/bindings/py/README.md b/bindings/py/README.md index cc2ca69c..905bb65c 100644 --- a/bindings/py/README.md +++ b/bindings/py/README.md @@ -22,6 +22,56 @@ interface overloads fail explicitly when a required native guard is missing. Generated `IReference` values are projected as `T | None`; native values, `None`, and generated `IReference_*` wrappers are accepted as inputs. +### Nullability in type stubs + +WinRT metadata does not record which values can be null, and most APIs raise +an exception instead of returning null. The generated `.pyi` stubs therefore +type the values you receive as non-null by default: method and property +results, async results and out values. For example, +`StorageFolder.create_file_async()` returns `WinRTCoroutine[StorageFile]`. + +These values keep `| None`: + +- `IReference` values, projected as `T | None` everywhere; +- results of `Try*` members, such as `try_get_item_async()` or + `JsonObject.try_parse()`, where null means "not found"; +- results of Windows SDK members whose documentation says they can return + null, such as `Accelerometer.get_default()`, + `DispatcherQueue.get_for_current_thread()` or + `StorageFolder.get_parent_async()`. The codegen embeds this list, derived + from the Windows SDK API reference; it does not cover Windows App SDK + (`Microsoft.*`) APIs; +- `Object`/`IInspectable` values (`DynWinRTValue | None`) and delegate-typed + values, which are often null. + +Reference-type elements read from WinRT collection interfaces are always typed +`T | None`, including vectors, views, iterables, iterators, map keys and values, +and key-value-pair keys and values. Here reference means the projection's +supported COM-pointer shapes: `Object`, interfaces, runtime classes, delegates, +and parameterized interfaces. Async wrappers are not collection element +shapes. Reference-type elements of returned WinRT arrays are also `T | None`; +the array value itself remains non-null. String, GUID, scalar, enum, and struct +array elements and keys remain non-null. A view or iterator obtained from a +mutable collection can expose a null slot, and WinRT collection interfaces do +not retain enough provenance for the stubs to distinguish that case. For +example, a `JsonArray` holds +`IJsonValue | None`, and `get_files_async()` returns +`WinRTCoroutine[Sequence[StorageFile | None]]`. Value-type elements remain +non-null. + +Mutable collections accept `None` when their element, map-key, or map-value +type is a WinRT reference type and store a real null WinRT value. This includes +`append()`, `insert()`, index and slice assignment, `extend()`, `update()` and +`setdefault()`. String, GUID, scalar, enum, and struct keys and value-type +elements reject `None` with `TypeError`. + +Other arguments keep accepting `None` where they did before. The stubs are +optimistic, like the generated TypeScript declarations: the runtime still +returns `None` when a WinRT API returns null, so check the API documentation +when a result can legitimately be absent. The inline annotations of the +generated `.py` modules, which `typing.get_type_hints()` and `--no-pyi` output +expose, still mark every object result `| None`. + ## Async WinRT operations Generated async methods return typed, asyncio-compatible operation objects: @@ -109,7 +159,9 @@ parameter such as `ThreadPool.run_async(handler)`, or a delegate-typed property) receives the delegate's arguments as projected Python values, typed from the delegate's `Invoke` signature. WinRT `Object` arguments stay `DynWinRTValue | None`, and `IReference` arguments are native values or -`None`. Async-operation arguments stay raw `DynWinRTValue` objects so a +`None`. Delegate-typed callback arguments are raw `DynWinRTValue | None` +because a null native delegate is passed to the callable as `None`. +Async-operation arguments stay raw `DynWinRTValue` objects so a callback projection cannot take over or cancel the operation's completion. For example, `map_changed` handlers of `PropertySet`, `StringMap`, `ValueSet`, and other `IObservableMap` implementations receive the @@ -127,10 +179,11 @@ value passed to a runtime class is reserved for wrapping an existing native instance before constructor overload dispatch. Keep the delegate object for a constructor, or pass the raw delegate to a named factory/method instead. -Callback parameter annotations are non-null by default, matching generated -method-output typing. This is an intentionally optimistic typing policy, not a -guarantee from the `Invoke` metadata: WinMD carries no nullability information, -and the runtime still passes `None` when WinRT supplies a null reference. +Callback parameter annotations are non-null by default except for `Object`, +`IReference`, and delegate-typed arguments. This is an intentionally +optimistic typing policy, not a guarantee from the `Invoke` metadata: WinMD +carries no nullability information, and the runtime still passes `None` when +WinRT supplies a null reference. Precise callback signatures live in the generated `.pyi` contract. Executable `.py` methods use the cycle-safe runtime annotation `Callable[..., object] | DynWinRTValue | DynWinRtDelegate`, so diff --git a/eng/ci/test_extract_null_results.py b/eng/ci/test_extract_null_results.py new file mode 100644 index 00000000..08900752 --- /dev/null +++ b/eng/ci/test_extract_null_results.py @@ -0,0 +1,111 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +import importlib.util +from pathlib import Path +import unittest + + +ROOT = Path(__file__).resolve().parents[2] +SCRIPT = ( + ROOT + / "tools" + / "dynwinrt-codegen" + / "scripts" + / "extract-null-results.py" +) +SPEC = importlib.util.spec_from_file_location("extract_null_results", SCRIPT) +assert SPEC is not None and SPEC.loader is not None +extractor = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(extractor) + + +def method_doc( + api_id: str, + *, + returns: str = "The current reading.", + remarks: str = "", +) -> str: + return f"""--- +-api-id: {api_id} +-api-type: winrt method +--- +## -returns +{returns} +## -remarks +{remarks} +""" + + +class NullResultExtractionTests(unittest.TestCase): + def test_required_return_null_check_is_nullable(self): + wording = ( + "Before using the return value from this method, the application " + "must first check that the value is not null. (If the value is " + "null and you attempt to retrieve it, Windows will generate an " + "exception.)" + ) + for sensor in ( + "Accelerometer", + "Compass", + "Gyrometer", + "Inclinometer", + "LightSensor", + "OrientationSensor", + ): + api_id = f"M:Windows.Devices.Sensors.{sensor}.GetCurrentReading" + with self.subTest(sensor=sensor): + self.assertEqual( + extractor.classify_document( + method_doc(api_id, remarks=wording) + ), + (api_id, "remarks"), + ) + + def test_negations_arguments_and_null_holders_are_not_nullable(self): + cases = ( + "This method always returns a value that is not null.", + ( + "Before using this method, check that the input parameter is " + "not null." + ), + "The returned object contains a JSON null value.", + ) + api_id = "M:Contoso.Sensor.GetCurrentReading" + for remarks in cases: + with self.subTest(remarks=remarks): + self.assertEqual( + extractor.classify_document( + method_doc(api_id, remarks=remarks) + ), + (api_id, None), + ) + + def test_direct_result_nullability_still_uses_result_sections(self): + method_id = "M:Contoso.Sensor.GetDefault" + self.assertEqual( + extractor.classify_document( + method_doc( + method_id, + returns="The default sensor, or null if none is installed.", + ) + ), + (method_id, "returns"), + ) + + property_id = "P:Contoso.Reading.OptionalValue" + property_doc = f"""--- +-api-id: {property_id} +-api-type: winrt property +--- +## -property-value +The current value, or null when no value is available. +""" + self.assertEqual( + extractor.classify_document(property_doc), + (property_id, "property-value"), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/samples/python/async-file-io/app.py b/samples/python/async-file-io/app.py index 56ed5804..7100aee4 100644 --- a/samples/python/async-file-io/app.py +++ b/samples/python/async-file-io/app.py @@ -15,12 +15,7 @@ async def run() -> None: with tempfile.TemporaryDirectory(prefix="dynwinrt-python-") as directory: with RoApartment(), projected_lifetime_scope(): folder = await StorageFolder.get_folder_from_path_async(directory) - if folder is None: - raise RuntimeError("StorageFolder returned no temporary folder") - file = await folder.create_file_async("sample.txt") - if file is None: - raise RuntimeError("StorageFolder returned no file") await FileIO.write_text_async(file, "Hello from dynwinrt.") await FileIO.append_text_async( file, diff --git a/samples/python/cryptography/app.py b/samples/python/cryptography/app.py index 2ff36fee..1dfc0397 100644 --- a/samples/python/cryptography/app.py +++ b/samples/python/cryptography/app.py @@ -9,12 +9,8 @@ def sha256(text: str) -> str: with RoApartment(), projected_lifetime_scope(): provider = HashAlgorithmProvider.open_algorithm("SHA256") - if provider is None: - raise RuntimeError("SHA256 provider is unavailable") data = IBuffer.from_bytes(text.encode("utf-8")) digest = provider.hash_data(data) - if digest is None: - raise RuntimeError("HashAlgorithmProvider returned no digest") copied_digest = digest.to_bytes() expected_length = provider.hash_length if len(copied_digest) != expected_length: diff --git a/samples/python/device-watcher/app.py b/samples/python/device-watcher/app.py index a1576d72..4dbada1a 100644 --- a/samples/python/device-watcher/app.py +++ b/samples/python/device-watcher/app.py @@ -15,8 +15,6 @@ async def enumerate_devices(timeout: int, show_names: bool) -> None: with RoApartment(), projected_lifetime_scope(): watcher = DeviceInformation.create_watcher() - if watcher is None: - raise RuntimeError("DeviceInformation returned no watcher") loop = asyncio.get_running_loop() enumeration_completed = asyncio.Event() diff --git a/samples/python/ocr-image/app.py b/samples/python/ocr-image/app.py index 90953ef8..ff9163e0 100644 --- a/samples/python/ocr-image/app.py +++ b/samples/python/ocr-image/app.py @@ -17,30 +17,21 @@ def normalized_words(value: str) -> set[str]: async def recognize(path: Path) -> str: with RoApartment(), projected_lifetime_scope(): file = await StorageFile.get_file_from_path_async(str(path.resolve())) - if file is None: - raise RuntimeError("StorageFile returned no image file") stream = await file.open_read_async() - if stream is None: - raise RuntimeError("StorageFile returned no image stream") decoder = await BitmapDecoder.create_async( stream.as_interface(IRandomAccessStream) ) - if decoder is None: - raise RuntimeError("BitmapDecoder returned no decoder") bitmap = await decoder.get_software_bitmap_async() - if bitmap is None: - raise RuntimeError("BitmapDecoder returned no SoftwareBitmap") with bitmap: + # Try* members return None instead of raising when nothing matches. engine = OcrEngine.try_create_from_user_profile_languages() if engine is None: raise RuntimeError( "No OCR engine is available for the user profile languages" ) result = await engine.recognize_async(bitmap) - if result is None: - raise RuntimeError("OcrEngine returned no result") return result.text diff --git a/samples/python/text-to-speech/app.py b/samples/python/text-to-speech/app.py index f6991911..b3afda46 100644 --- a/samples/python/text-to-speech/app.py +++ b/samples/python/text-to-speech/app.py @@ -13,8 +13,6 @@ async def speak(text: str, smoke: bool) -> None: with RoApartment(), projected_lifetime_scope(): with SpeechSynthesizer() as synthesizer: stream = await synthesizer.synthesize_text_to_stream_async(text) - if stream is None: - raise RuntimeError("SpeechSynthesizer returned no stream") with stream: if smoke: diff --git a/tests/e2e/runners/py_runner.py b/tests/e2e/runners/py_runner.py index a761965b..099e77a1 100644 --- a/tests/e2e/runners/py_runner.py +++ b/tests/e2e/runners/py_runner.py @@ -505,26 +505,159 @@ async def run_check( cr['pass'] = True elif kind == 'nullable_object_array_roundtrip': + from uuid import UUID + uri_cls = generated_type(pkg_name, 'Uri') + property_value_cls = generated_type(pkg_name, 'IPropertyValue') + property_type_cls = generated_type(pkg_name, 'PropertyType') uri = uri_cls.create_uri('https://example.com/null-array') - boxed = getattr(cls, member)( - [dw.DynWinRTValue.null_value(), uri._obj] - ) + + boxed = getattr(cls, member)([dw.DynWinRTValue.null_value(), uri._obj]) if boxed is None: - cr['error'] = 'CreateInspectableArray returned None' + cr['error'] = 'create_inspectable_array returned None' return cr - values = boxed.call_0( - 38, - dw.DynWinRTType.array_type(dw.DynWinRTType.object()), - ).as_array().to_values() + try: + property_value = property_value_cls.from_value(boxed) + finally: + boxed.release() + try: + if property_value.type != property_type_cls.InspectableArray: + cr['error'] = ( + 'inspectable array reported property type ' + f'{property_value.type!r}' + ) + return cr + if property_value.is_numeric_scalar: + cr['error'] = 'inspectable array reported a numeric scalar' + return cr + values = property_value.get_inspectable_array() + finally: + dw.release_projected(property_value) + if len(values) != 2: cr['error'] = f'expected 2 inspectable values, got {len(values)}' - elif not values[0].is_null(): + elif values[0] is not None: cr['error'] = 'null inspectable array element was not preserved' + elif values[1] is None: + cr['error'] = 'non-null inspectable array element became None' elif values[1].identity_raw() != uri._obj.identity_raw(): cr['error'] = 'inspectable array element lost COM identity' else: cr['pass'] = True + if len(values) > 1 and values[1] is not None: + values[1].release() + if not cr['pass']: + return cr + + def unused(_self): + raise AssertionError('unexpected IPropertyValue callback') + + value_names = ( + 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', + 'uint64', 'single', 'double', 'char16', 'boolean', 'string', + 'guid', 'date_time', 'time_span', 'point', 'size', 'rect', + ) + callbacks = { + f'get_{name}': unused + for name in value_names + } + callbacks.update({ + f'get_{name}_array': unused + for name in value_names + }) + scalar_values = { + 'get_uint8': 200, + 'get_int16': -1234, + 'get_uint16': 54321, + 'get_int32': -123456, + 'get_uint32': 4_000_000_000, + 'get_int64': -5_000_000_000, + 'get_uint64': 10_000_000_000, + 'get_single': 1.25, + 'get_double': -2.5, + 'get_char16': 'Z', + 'get_boolean': True, + 'get_string': 'dynwinrt', + 'get_guid': UUID('01234567-89ab-cdef-0123-456789abcdef'), + } + array_values = { + 'get_uint8_array': bytes([0, 255]), + 'get_int16_array': [-1234, 2345], + 'get_uint16_array': [0, 65535], + 'get_int32_array': [1, -2], + 'get_uint32_array': [0, 4_000_000_000], + 'get_int64_array': [-5_000_000_000, 5_000_000_000], + 'get_uint64_array': [0, 10_000_000_000], + 'get_single_array': [1.25, -0.5], + 'get_double_array': [2.5, -4.0], + 'get_char16_array': ['A', 'Z'], + 'get_boolean_array': [True, False], + 'get_string_array': ['first', 'second'], + 'get_guid_array': [scalar_values['get_guid']], + } + + def returning(value): + return lambda _self: value + + callbacks.update({ + name: returning(value) + for name, value in scalar_values.items() + }) + callbacks.update({ + name: returning(value) + for name, value in array_values.items() + }) + callbacks.update({ + 'get_type': lambda _self: property_type_cls.Int32, + 'get_is_numeric_scalar': lambda _self: True, + 'get_inspectable_array': lambda _self: [None, uri._obj], + }) + handlers = type('PropertyValueHandlers', (), callbacks)() + with property_value_cls.implement(handlers) as implementation: + view = implementation.value + if view.type != property_type_cls.Int32: + cr['error'] = 'implemented property value returned the wrong type' + return cr + if not view.is_numeric_scalar: + cr['error'] = 'implemented property value was not numeric' + return cr + for getter, expected in scalar_values.items(): + actual = getattr(view, getter)() + if actual != expected: + cr['error'] = ( + f'implemented {getter} returned {actual!r}, ' + f'expected {expected!r}' + ) + return cr + for getter, expected in array_values.items(): + actual = getattr(view, getter)() + if actual != expected: + cr['error'] = ( + f'implemented {getter} returned {actual!r}, ' + f'expected {expected!r}' + ) + return cr + values = view.get_inspectable_array() + try: + if len(values) != 2 or values[0] is not None: + cr['error'] = ( + 'implemented inspectable array did not preserve null' + ) + return cr + if values[1] is None: + cr['error'] = ( + 'implemented inspectable array lost its object' + ) + return cr + if values[1].identity_raw() != uri._obj.identity_raw(): + cr['error'] = ( + 'implemented inspectable array lost COM identity' + ) + return cr + finally: + if len(values) > 1 and values[1] is not None: + values[1].release() + cr['pass'] = True elif kind == 'projection_identity': import weakref diff --git a/tests/e2e/typecheck/python_generated_api.py b/tests/e2e/typecheck/python_generated_api.py index 377cb1cd..f02c3a4d 100644 --- a/tests/e2e/typecheck/python_generated_api.py +++ b/tests/e2e/typecheck/python_generated_api.py @@ -33,16 +33,12 @@ IObservableMap_String_String, PropertySet, StringMap, -) -from python_bindings.windows.system.threading import ( - ThreadPool, - ThreadPoolTimer, - WorkItemOptions, - WorkItemPriority, + ValueSet, ) from python_bindings.windows.globalization import Calendar from python_bindings.windows.globalization.number_formatting import DecimalFormatter from python_bindings.windows.storage import ( + IStorageItem, NameCollisionOption, StorageFile, StorageFolder, @@ -55,6 +51,12 @@ IOutputStream, RandomAccessStream, ) +from python_bindings.windows.system.threading import ( + ThreadPool, + ThreadPoolTimer, + WorkItemOptions, + WorkItemPriority, +) class StructuralAsyncAdapter: @@ -95,8 +97,9 @@ def check_uri() -> None: uri: Uri = Uri("https://example.com") relative: Uri = Uri("https://example.com/root/", "child") host: str = uri.host - combined: Uri | None = uri.combine_uri("child") - _: Tuple[str, Uri, Uri | None] = (host, relative, combined) + combined: Uri = uri.combine_uri("child") + absolute: str = combined.absolute_uri + _: Tuple[str, Uri, Uri, str] = (host, relative, combined, absolute) def check_nullable_value( @@ -112,8 +115,7 @@ def check_nullable_value( def check_string_vector(calendar: Calendar) -> None: - languages: Sequence[str] | None = calendar.languages - assert languages is not None + languages: Sequence[str] = calendar.languages first: str = languages[0] located: int = languages.index(first) many: List[str] = list(languages[:4]) @@ -200,7 +202,7 @@ async def check_documented_async_overload_names( target: InMemoryRandomAccessStream, ) -> None: option = NameCollisionOption.ReplaceExisting - copies: List[StorageFile | None] = [ + copies: List[StorageFile] = [ await file.copy_async(folder), await file.copy_async(folder, "copy.txt"), await file.copy_async(folder, "copy.txt", option), @@ -212,7 +214,7 @@ async def check_documented_async_overload_names( await RandomAccessStream.copy_size_async(source, target, 4), ] writer: DataWriter = DataWriter(source) - _: Tuple[List[StorageFile | None], List[int], DataWriter] = (copies, copied, writer) + _: Tuple[List[StorageFile], List[int], DataWriter] = (copies, copied, writer) def check_ibuffer_bytes() -> None: @@ -223,6 +225,16 @@ def check_ibuffer_bytes() -> None: _: Tuple[bytes, bytes] = (interface_bytes, runtime_bytes) +async def check_output_nullability(folder: StorageFolder, values: ValueSet) -> None: + created: StorageFile = await folder.create_file_async("notes.txt") + names: List[str] = [ + item.name for item in await folder.get_files_async() if item is not None + ] + assert_type(folder.try_get_item_async("notes.txt"), WinRTCoroutine[IStorageItem | None]) + assert_type(values["key"], DynWinRTValue | None) + _: Tuple[StorageFile, List[str]] = (created, names) + + def check_map_changed_handlers(properties: PropertySet, strings: StringMap) -> None: def on_properties( sender: IObservableMap_String_Object, args: IMapChangedEventArgs_String @@ -233,7 +245,6 @@ def on_properties( _: Tuple[int, DynWinRTValue | None, CollectionChange] = (size, value, change) unsubscribe: Callable[[], None] = properties.subscribe_map_changed(on_properties) - # Lambda parameters are inferred from the typed callback, not Any. properties.once_map_changed( lambda sender, args: assert_type(sender, IObservableMap_String_Object) ) diff --git a/tools/dynwinrt-codegen/api-docs/windows-null-results.overrides.txt b/tools/dynwinrt-codegen/api-docs/windows-null-results.overrides.txt new file mode 100644 index 00000000..912ee303 --- /dev/null +++ b/tools/dynwinrt-codegen/api-docs/windows-null-results.overrides.txt @@ -0,0 +1,17 @@ +# Reviewed corrections for windows-null-results.txt, applied last by +# scripts/extract-null-results.py. "+pattern" adds and "-pattern" removes +# members. A pattern is an api-id that may use fnmatch wildcards; it must match +# a member documented at the pinned winrt-api commit. + +# Sensors report a missing device through null across the whole family, as +# most of their pages state ("or null if no integrated ... are found"). ++M:Windows.Devices.Sensors.*.GetDefault* + +# Device lookups complete with null when the device is missing or access is +# denied, as many of their pages state. ++M:Windows.Devices.*.FromIdAsync(*) ++M:Windows.Devices.*.GetDefaultAsync* + +# Overridable implementation callbacks, not results that consumers receive. +-M:Windows.UI.Xaml.Automation.Peers.AutomationPeer.GetPatternCore(*) +-M:Windows.UI.Xaml.Controls.StyleSelector.SelectStyleCore(*) diff --git a/tools/dynwinrt-codegen/api-docs/windows-null-results.txt b/tools/dynwinrt-codegen/api-docs/windows-null-results.txt new file mode 100644 index 00000000..51c1b97b --- /dev/null +++ b/tools/dynwinrt-codegen/api-docs/windows-null-results.txt @@ -0,0 +1,946 @@ +# Windows SDK members whose documented result can be null: a method's +# return value (for asynchronous methods, the completed result) or a +# property's value. dynwinrt-codegen keeps `| None` on these outputs. +# Source: https://github.com/MicrosoftDocs/winrt-api at commit 8448d5eecfbc2ed903f659f350841dcb4888bc8b +# Generated by scripts/extract-null-results.py; do not edit. Reviewed +# corrections belong in windows-null-results.overrides.txt. +M:Windows.AI.MachineLearning.TensorString.CreateReference +M:Windows.ApplicationModel.AppInstance.GetActivatedEventArgs +M:Windows.ApplicationModel.Calls.PhoneCall.GetFromId(System.String) +M:Windows.ApplicationModel.Calls.PhoneLineTransportDevice.FromId(System.String) +M:Windows.ApplicationModel.Contacts.ContactStore.GetContactListAsync(System.String) +M:Windows.ApplicationModel.ConversationalAgent.ActivationSignalDetectionConfiguration.GetModelData +M:Windows.ApplicationModel.ConversationalAgent.ActivationSignalDetectionConfiguration.GetModelDataAsync +M:Windows.ApplicationModel.ConversationalAgent.ActivationSignalDetectionConfiguration.GetModelDataType +M:Windows.ApplicationModel.ConversationalAgent.ActivationSignalDetectionConfiguration.GetModelDataTypeAsync +M:Windows.ApplicationModel.Package.GetContentGroupAsync(System.String) +M:Windows.ApplicationModel.UserDataAccounts.UserDataAccountManager.RequestStoreAsync(Windows.ApplicationModel.UserDataAccounts.UserDataAccountStoreAccessType) +M:Windows.ApplicationModel.UserDataTasks.UserDataTaskStore.GetListAsync(System.String) +M:Windows.ApplicationModel.Wallet.WalletItemStore.GetWalletItemAsync(System.String) +M:Windows.Data.Xml.Dom.DtdEntity.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.DtdEntity.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.DtdEntity.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.DtdEntity.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.DtdEntity.SelectNodesNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.DtdEntity.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.DtdEntity.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.DtdNotation.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.DtdNotation.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.DtdNotation.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.DtdNotation.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.DtdNotation.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.DtdNotation.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.IXmlNode.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.IXmlNode.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.IXmlNode.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.IXmlNode.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.IXmlNodeSelector.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.IXmlNodeSelector.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.XmlAttribute.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlAttribute.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlAttribute.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlAttribute.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlAttribute.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.XmlAttribute.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.XmlCDataSection.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlCDataSection.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlCDataSection.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlCDataSection.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlCDataSection.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.XmlCDataSection.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.XmlComment.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlComment.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlComment.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlComment.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlComment.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.XmlComment.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.XmlDocument.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocument.GetElementById(System.String) +M:Windows.Data.Xml.Dom.XmlDocument.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocument.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocument.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocument.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.XmlDocument.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.XmlDocumentFragment.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocumentFragment.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocumentFragment.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocumentFragment.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocumentFragment.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.XmlDocumentFragment.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.XmlDocumentType.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocumentType.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocumentType.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocumentType.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlDocumentType.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.XmlDocumentType.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.XmlElement.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlElement.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlElement.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlElement.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlElement.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.XmlElement.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.XmlElement.SetAttributeNodeNS(Windows.Data.Xml.Dom.XmlAttribute) +M:Windows.Data.Xml.Dom.XmlEntityReference.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlEntityReference.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlEntityReference.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlEntityReference.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlEntityReference.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.XmlEntityReference.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.XmlNamedNodeMap.GetNamedItem(System.String) +M:Windows.Data.Xml.Dom.XmlNamedNodeMap.GetNamedItemNS(System.Object,System.String) +M:Windows.Data.Xml.Dom.XmlNamedNodeMap.Item(System.UInt32) +M:Windows.Data.Xml.Dom.XmlNamedNodeMap.RemoveNamedItem(System.String) +M:Windows.Data.Xml.Dom.XmlNamedNodeMap.RemoveNamedItemNS(System.Object,System.String) +M:Windows.Data.Xml.Dom.XmlNamedNodeMap.SetNamedItem(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlNamedNodeMap.SetNamedItemNS(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlNodeList.Item(System.UInt32) +M:Windows.Data.Xml.Dom.XmlProcessingInstruction.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlProcessingInstruction.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlProcessingInstruction.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlProcessingInstruction.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlProcessingInstruction.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.XmlProcessingInstruction.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Data.Xml.Dom.XmlText.AppendChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlText.InsertBefore(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlText.RemoveChild(Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlText.ReplaceChild(Windows.Data.Xml.Dom.IXmlNode,Windows.Data.Xml.Dom.IXmlNode) +M:Windows.Data.Xml.Dom.XmlText.SelectSingleNode(System.String) +M:Windows.Data.Xml.Dom.XmlText.SelectSingleNodeNS(System.String,System.Object) +M:Windows.Devices.Adc.AdcController.GetDefaultAsync +M:Windows.Devices.AllJoyn.AllJoynServiceInfo.FromIdAsync(System.String) +M:Windows.Devices.Bluetooth.BluetoothAdapter.FromIdAsync(System.String) +M:Windows.Devices.Bluetooth.BluetoothAdapter.GetDefaultAsync +M:Windows.Devices.Bluetooth.BluetoothDevice.FromBluetoothAddressAsync(System.UInt64) +M:Windows.Devices.Bluetooth.BluetoothDevice.FromIdAsync(System.String) +M:Windows.Devices.Bluetooth.BluetoothLEDevice.FromBluetoothAddressAsync(System.UInt64) +M:Windows.Devices.Bluetooth.BluetoothLEDevice.FromBluetoothAddressAsync(System.UInt64,Windows.Devices.Bluetooth.BluetoothAddressType) +M:Windows.Devices.Bluetooth.BluetoothLEDevice.FromIdAsync(System.String) +M:Windows.Devices.Bluetooth.GenericAttributeProfile.GattDeviceService.FromIdAsync(System.String) +M:Windows.Devices.Bluetooth.GenericAttributeProfile.GattDeviceService.FromIdAsync(System.String,Windows.Devices.Bluetooth.GenericAttributeProfile.GattSharingMode) +M:Windows.Devices.Bluetooth.Rfcomm.RfcommDeviceService.FromIdAsync(System.String) +M:Windows.Devices.Custom.CustomDevice.FromIdAsync(System.String,Windows.Devices.Custom.DeviceAccessMode,Windows.Devices.Custom.DeviceSharingMode) +M:Windows.Devices.Display.Core.DisplayTarget.TryGetMonitor +M:Windows.Devices.Display.DisplayMonitor.FromIdAsync(System.String) +M:Windows.Devices.Gpio.GpioController.GetDefault +M:Windows.Devices.Gpio.GpioController.GetDefaultAsync +M:Windows.Devices.Haptics.InputHapticsManager.TryGetForThread(System.UInt32) +M:Windows.Devices.Haptics.VibrationDevice.FromIdAsync(System.String) +M:Windows.Devices.Haptics.VibrationDevice.GetDefaultAsync +M:Windows.Devices.HumanInterfaceDevice.HidDevice.FromIdAsync(System.String,Windows.Storage.FileAccessMode) +M:Windows.Devices.I2c.I2cController.GetDefaultAsync +M:Windows.Devices.I2c.I2cDevice.FromIdAsync(System.String,Windows.Devices.I2c.I2cConnectionSettings) +M:Windows.Devices.I2c.II2cDeviceStatics.FromIdAsync(System.String,Windows.Devices.I2c.I2cConnectionSettings) +M:Windows.Devices.Input.PenDevice.GetFromPointerId(System.UInt32) +M:Windows.Devices.Lights.Lamp.FromIdAsync(System.String) +M:Windows.Devices.Lights.Lamp.GetDefaultAsync +M:Windows.Devices.Lights.LampArray.FromIdAsync(System.String) +M:Windows.Devices.Midi.MidiInPort.FromIdAsync(System.String) +M:Windows.Devices.Midi.MidiOutPort.FromIdAsync(System.String) +M:Windows.Devices.Perception.PerceptionColorFrameReader.TryReadLatestFrame +M:Windows.Devices.Perception.PerceptionColorFrameSource.AcquireControlSession +M:Windows.Devices.Perception.PerceptionColorFrameSource.FromIdAsync(System.String) +M:Windows.Devices.Perception.PerceptionColorFrameSource.TryGetDepthCorrelatedCameraIntrinsicsAsync(Windows.Devices.Perception.PerceptionDepthFrameSource) +M:Windows.Devices.Perception.PerceptionColorFrameSource.TryGetDepthCorrelatedCoordinateMapperAsync(System.String,Windows.Devices.Perception.PerceptionDepthFrameSource) +M:Windows.Devices.Perception.PerceptionDepthFrameReader.TryReadLatestFrame +M:Windows.Devices.Perception.PerceptionDepthFrameSource.AcquireControlSession +M:Windows.Devices.Perception.PerceptionDepthFrameSource.FromIdAsync(System.String) +M:Windows.Devices.Perception.PerceptionDepthFrameSource.TryGetDepthCorrelatedCameraIntrinsicsAsync(Windows.Devices.Perception.PerceptionDepthFrameSource) +M:Windows.Devices.Perception.PerceptionDepthFrameSource.TryGetDepthCorrelatedCoordinateMapperAsync(System.String,Windows.Devices.Perception.PerceptionDepthFrameSource) +M:Windows.Devices.Perception.PerceptionInfraredFrameReader.TryReadLatestFrame +M:Windows.Devices.Perception.PerceptionInfraredFrameSource.AcquireControlSession +M:Windows.Devices.Perception.PerceptionInfraredFrameSource.FromIdAsync(System.String) +M:Windows.Devices.Perception.PerceptionInfraredFrameSource.TryGetDepthCorrelatedCameraIntrinsicsAsync(Windows.Devices.Perception.PerceptionDepthFrameSource) +M:Windows.Devices.Perception.PerceptionInfraredFrameSource.TryGetDepthCorrelatedCoordinateMapperAsync(System.String,Windows.Devices.Perception.PerceptionDepthFrameSource) +M:Windows.Devices.Perception.Provider.IPerceptionFrameProviderManager.GetFrameProvider(Windows.Devices.Perception.Provider.PerceptionFrameProviderInfo) +M:Windows.Devices.PointOfService.BarcodeScanner.FromIdAsync(System.String) +M:Windows.Devices.PointOfService.BarcodeScanner.GetDefaultAsync +M:Windows.Devices.PointOfService.CashDrawer.FromIdAsync(System.String) +M:Windows.Devices.PointOfService.CashDrawer.GetDefaultAsync +M:Windows.Devices.PointOfService.ClaimedLineDisplay.FromIdAsync(System.String) +M:Windows.Devices.PointOfService.LineDisplay.FromIdAsync(System.String) +M:Windows.Devices.PointOfService.LineDisplay.GetDefaultAsync +M:Windows.Devices.PointOfService.MagneticStripeReader.ClaimReaderAsync +M:Windows.Devices.PointOfService.MagneticStripeReader.FromIdAsync(System.String) +M:Windows.Devices.PointOfService.MagneticStripeReader.GetDefaultAsync +M:Windows.Devices.PointOfService.PosPrinter.FromIdAsync(System.String) +M:Windows.Devices.PointOfService.PosPrinter.GetDefaultAsync +M:Windows.Devices.Power.Battery.FromIdAsync(System.String) +M:Windows.Devices.Printers.Print3DDevice.FromIdAsync(System.String) +M:Windows.Devices.Pwm.PwmController.FromIdAsync(System.String) +M:Windows.Devices.Pwm.PwmController.GetDefaultAsync +M:Windows.Devices.Radios.Radio.FromIdAsync(System.String) +M:Windows.Devices.Scanners.ImageScanner.FromIdAsync(System.String) +M:Windows.Devices.Sensors.Accelerometer.FromIdAsync(System.String) +M:Windows.Devices.Sensors.Accelerometer.GetCurrentReading +M:Windows.Devices.Sensors.Accelerometer.GetDefault +M:Windows.Devices.Sensors.Accelerometer.GetDefault(Windows.Devices.Sensors.AccelerometerReadingType) +M:Windows.Devices.Sensors.Accelerometer.GetDeviceSelector(Windows.Devices.Sensors.AccelerometerReadingType) +M:Windows.Devices.Sensors.ActivitySensor.FromIdAsync(System.String) +M:Windows.Devices.Sensors.ActivitySensor.GetDefaultAsync +M:Windows.Devices.Sensors.ActivitySensor.GetDeviceSelector +M:Windows.Devices.Sensors.Altimeter.GetDefault +M:Windows.Devices.Sensors.Barometer.FromIdAsync(System.String) +M:Windows.Devices.Sensors.Barometer.GetDefault +M:Windows.Devices.Sensors.Barometer.GetDeviceSelector +M:Windows.Devices.Sensors.Compass.FromIdAsync(System.String) +M:Windows.Devices.Sensors.Compass.GetCurrentReading +M:Windows.Devices.Sensors.Compass.GetDefault +M:Windows.Devices.Sensors.Compass.GetDeviceSelector +M:Windows.Devices.Sensors.Custom.CustomSensor.FromIdAsync(System.String) +M:Windows.Devices.Sensors.Custom.CustomSensor.GetCurrentReading +M:Windows.Devices.Sensors.Custom.CustomSensor.GetDeviceSelector(System.Guid) +M:Windows.Devices.Sensors.Gyrometer.FromIdAsync(System.String) +M:Windows.Devices.Sensors.Gyrometer.GetCurrentReading +M:Windows.Devices.Sensors.Gyrometer.GetDefault +M:Windows.Devices.Sensors.Gyrometer.GetDeviceSelector +M:Windows.Devices.Sensors.HingeAngleSensor.FromIdAsync(System.String) +M:Windows.Devices.Sensors.HingeAngleSensor.GetDefaultAsync +M:Windows.Devices.Sensors.HingeAngleSensor.GetDeviceSelector +M:Windows.Devices.Sensors.HumanPresenceSensor.FromIdAsync(System.String) +M:Windows.Devices.Sensors.HumanPresenceSensor.GetDefault +M:Windows.Devices.Sensors.HumanPresenceSensor.GetDefaultAsync +M:Windows.Devices.Sensors.Inclinometer.FromIdAsync(System.String) +M:Windows.Devices.Sensors.Inclinometer.GetCurrentReading +M:Windows.Devices.Sensors.Inclinometer.GetDefault +M:Windows.Devices.Sensors.Inclinometer.GetDefault(Windows.Devices.Sensors.SensorReadingType) +M:Windows.Devices.Sensors.Inclinometer.GetDefaultForRelativeReadings +M:Windows.Devices.Sensors.Inclinometer.GetDeviceSelector(Windows.Devices.Sensors.SensorReadingType) +M:Windows.Devices.Sensors.LightSensor.FromIdAsync(System.String) +M:Windows.Devices.Sensors.LightSensor.GetCurrentReading +M:Windows.Devices.Sensors.LightSensor.GetDefault +M:Windows.Devices.Sensors.LightSensor.GetDeviceSelector +M:Windows.Devices.Sensors.Magnetometer.FromIdAsync(System.String) +M:Windows.Devices.Sensors.Magnetometer.GetDefault +M:Windows.Devices.Sensors.Magnetometer.GetDeviceSelector +M:Windows.Devices.Sensors.OrientationSensor.FromIdAsync(System.String) +M:Windows.Devices.Sensors.OrientationSensor.GetCurrentReading +M:Windows.Devices.Sensors.OrientationSensor.GetDefault +M:Windows.Devices.Sensors.OrientationSensor.GetDefault(Windows.Devices.Sensors.SensorReadingType) +M:Windows.Devices.Sensors.OrientationSensor.GetDefault(Windows.Devices.Sensors.SensorReadingType,Windows.Devices.Sensors.SensorOptimizationGoal) +M:Windows.Devices.Sensors.OrientationSensor.GetDefaultForRelativeReadings +M:Windows.Devices.Sensors.OrientationSensor.GetDeviceSelector(Windows.Devices.Sensors.SensorReadingType) +M:Windows.Devices.Sensors.OrientationSensor.GetDeviceSelector(Windows.Devices.Sensors.SensorReadingType,Windows.Devices.Sensors.SensorOptimizationGoal) +M:Windows.Devices.Sensors.Pedometer.FromIdAsync(System.String) +M:Windows.Devices.Sensors.Pedometer.GetDefaultAsync +M:Windows.Devices.Sensors.Pedometer.GetDeviceSelector +M:Windows.Devices.Sensors.ProximitySensor.GetDeviceSelector +M:Windows.Devices.Sensors.SimpleOrientationSensor.FromIdAsync(System.String) +M:Windows.Devices.Sensors.SimpleOrientationSensor.GetDefault +M:Windows.Devices.Sensors.SimpleOrientationSensor.GetDeviceSelector +M:Windows.Devices.SerialCommunication.SerialDevice.FromIdAsync(System.String) +M:Windows.Devices.SmartCards.SmartCardEmulator.GetDefaultAsync +M:Windows.Devices.SmartCards.SmartCardReader.FromIdAsync(System.String) +M:Windows.Devices.Sms.SmsDevice.FromIdAsync(System.String) +M:Windows.Devices.Sms.SmsDevice.GetDefaultAsync +M:Windows.Devices.Spi.ISpiDeviceStatics.FromIdAsync(System.String,Windows.Devices.Spi.SpiConnectionSettings) +M:Windows.Devices.Spi.SpiController.GetDefaultAsync +M:Windows.Devices.Spi.SpiDevice.FromIdAsync(System.String,Windows.Devices.Spi.SpiConnectionSettings) +M:Windows.Devices.Usb.UsbDevice.FromIdAsync(System.String) +M:Windows.Devices.WiFi.WiFiAdapter.FromIdAsync(System.String) +M:Windows.Devices.WiFiDirect.Services.WiFiDirectService.FromIdAsync(System.String) +M:Windows.Devices.WiFiDirect.WiFiDirectDevice.FromIdAsync(System.String) +M:Windows.Devices.WiFiDirect.WiFiDirectDevice.FromIdAsync(System.String,Windows.Devices.WiFiDirect.WiFiDirectConnectionParameters) +M:Windows.Foundation.Diagnostics.FileLoggingSession.CloseAndSaveToFileAsync +M:Windows.Gaming.Input.Custom.GameControllerFactoryManager.TryGetFactoryControllerFromGameController(Windows.Gaming.Input.Custom.ICustomGameControllerFactory,Windows.Gaming.Input.IGameController) +M:Windows.Gaming.Input.Preview.LegacyGipGameControllerProvider.FromGameController(Windows.Gaming.Input.IGameController) +M:Windows.Gaming.Input.Preview.LegacyGipGameControllerProvider.FromGameControllerProvider(Windows.Gaming.Input.Custom.IGameControllerProvider) +M:Windows.Gaming.Input.Preview.LegacyGipGameControllerProvider.IsCopilot(Windows.System.User,System.String) +M:Windows.Gaming.Input.Preview.LegacyGipGameControllerProvider.IsPilot(Windows.System.User,System.String) +M:Windows.Gaming.UI.GameChatOverlay.GetDefault +M:Windows.Gaming.UI.GameMonitor.GetDefault +M:Windows.Globalization.NumberFormatting.CurrencyFormatter.ParseDouble(System.String) +M:Windows.Globalization.NumberFormatting.CurrencyFormatter.ParseInt(System.String) +M:Windows.Globalization.NumberFormatting.CurrencyFormatter.ParseUInt(System.String) +M:Windows.Globalization.NumberFormatting.DecimalFormatter.ParseDouble(System.String) +M:Windows.Globalization.NumberFormatting.DecimalFormatter.ParseInt(System.String) +M:Windows.Globalization.NumberFormatting.DecimalFormatter.ParseUInt(System.String) +M:Windows.Globalization.NumberFormatting.INumberParser.ParseDouble(System.String) +M:Windows.Globalization.NumberFormatting.INumberParser.ParseInt(System.String) +M:Windows.Globalization.NumberFormatting.INumberParser.ParseUInt(System.String) +M:Windows.Globalization.NumberFormatting.PercentFormatter.ParseDouble(System.String) +M:Windows.Globalization.NumberFormatting.PercentFormatter.ParseInt(System.String) +M:Windows.Globalization.NumberFormatting.PercentFormatter.ParseUInt(System.String) +M:Windows.Globalization.NumberFormatting.PermilleFormatter.ParseDouble(System.String) +M:Windows.Globalization.NumberFormatting.PermilleFormatter.ParseInt(System.String) +M:Windows.Globalization.NumberFormatting.PermilleFormatter.ParseUInt(System.String) +M:Windows.Graphics.Capture.Direct3D11CaptureFramePool.TryGetNextFrame +M:Windows.Graphics.Holographic.HolographicCameraPose.TryGetCullingFrustum(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.Graphics.Holographic.HolographicCameraPose.TryGetViewTransform(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.Graphics.Holographic.HolographicCameraPose.TryGetVisibleFrustum(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.Graphics.Holographic.HolographicDisplay.TryGetViewConfiguration(Windows.Graphics.Holographic.HolographicViewConfigurationKind) +M:Windows.Graphics.Printing.Workflow.PrintWorkflowVirtualPrinterDataAvailableEventArgs.GetTargetFileAsync +M:Windows.Management.Deployment.PackageManager.FindPackage(System.String) +M:Windows.Management.Update.PreviewBuildsManager.GetDefault +M:Windows.Management.Update.WindowsUpdate.GetPropertyValue(System.String) +M:Windows.Media.Audio.AudioPlaybackConnection.TryCreateFromId(System.String) +M:Windows.Media.Capture.AppBroadcastStreamReader.TryGetNextAudioFrame +M:Windows.Media.Capture.AppBroadcastStreamReader.TryGetNextVideoFrame +M:Windows.Media.Capture.Frames.DepthMediaFrame.TryCreateCoordinateMapper(Windows.Media.Devices.Core.CameraIntrinsics,Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.Media.Capture.Frames.MediaFrameSource.TryGetCameraIntrinsics(Windows.Media.Capture.Frames.MediaFrameFormat) +M:Windows.Media.Devices.CallControl.FromId(System.String) +M:Windows.Media.Devices.CallControl.GetDefault +M:Windows.Media.Ocr.OcrEngine.TryCreateFromLanguage(Windows.Globalization.Language) +M:Windows.Media.Ocr.OcrEngine.TryCreateFromUserProfileLanguages +M:Windows.Networking.Connectivity.NetworkInformation.GetInternetConnectionProfile +M:Windows.Networking.NetworkOperators.ESimManager.TryCreateESimWatcher +M:Windows.Networking.Proximity.ProximityDevice.GetDefault +M:Windows.Networking.XboxLive.XboxLiveEndpointPair.FindEndpointPairByHostNamesAndPorts(Windows.Networking.HostName,System.String,Windows.Networking.HostName,System.String) +M:Windows.Networking.XboxLive.XboxLiveEndpointPair.FindEndpointPairBySocketAddressBytes(System.Byte[],System.Byte[]) +M:Windows.Perception.Spatial.SpatialAnchor.TryCreateRelativeTo(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.Perception.Spatial.SpatialAnchor.TryCreateRelativeTo(Windows.Perception.Spatial.SpatialCoordinateSystem,Windows.Foundation.Numerics.Vector3) +M:Windows.Perception.Spatial.SpatialAnchor.TryCreateRelativeTo(Windows.Perception.Spatial.SpatialCoordinateSystem,Windows.Foundation.Numerics.Vector3,Windows.Foundation.Numerics.Quaternion) +M:Windows.Perception.Spatial.SpatialCoordinateSystem.TryGetTransformTo(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.Perception.Spatial.SpatialLocatorAttachedFrameOfReference.TryGetRelativeHeadingAtTimestamp(Windows.Perception.PerceptionTimestamp) +M:Windows.Perception.Spatial.SpatialStageFrameOfReference.TryGetMovementBounds(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.Perception.Spatial.Surfaces.SpatialSurfaceInfo.TryComputeLatestMeshAsync(System.Double) +M:Windows.Perception.Spatial.Surfaces.SpatialSurfaceInfo.TryGetBounds(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.Services.Maps.Guidance.GuidanceRoute.TryCreateFromMapRoute(Windows.Services.Maps.MapRoute) +M:Windows.Storage.FileProperties.StorageItemContentProperties.RetrievePropertiesAsync(Windows.Foundation.Collections.IIterable{System.String}) +M:Windows.Storage.IStorageItem2.GetParentAsync +M:Windows.Storage.IStorageItemProperties.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode) +M:Windows.Storage.IStorageItemProperties.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32) +M:Windows.Storage.IStorageItemProperties.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32,Windows.Storage.FileProperties.ThumbnailOptions) +M:Windows.Storage.IStorageItemProperties2.GetScaledImageAsThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode) +M:Windows.Storage.IStorageItemProperties2.GetScaledImageAsThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32) +M:Windows.Storage.IStorageItemProperties2.GetScaledImageAsThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32,Windows.Storage.FileProperties.ThumbnailOptions) +M:Windows.Storage.StorageFile.GetScaledImageAsThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode) +M:Windows.Storage.StorageFile.GetScaledImageAsThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32) +M:Windows.Storage.StorageFile.GetScaledImageAsThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32,Windows.Storage.FileProperties.ThumbnailOptions) +M:Windows.Storage.StorageFile.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode) +M:Windows.Storage.StorageFile.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32) +M:Windows.Storage.StorageFile.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32,Windows.Storage.FileProperties.ThumbnailOptions) +M:Windows.Storage.StorageFolder.GetScaledImageAsThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode) +M:Windows.Storage.StorageFolder.GetScaledImageAsThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32) +M:Windows.Storage.StorageFolder.GetScaledImageAsThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32,Windows.Storage.FileProperties.ThumbnailOptions) +M:Windows.Storage.StorageFolder.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode) +M:Windows.Storage.StorageFolder.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32) +M:Windows.Storage.StorageFolder.GetThumbnailAsync(Windows.Storage.FileProperties.ThumbnailMode,System.UInt32,Windows.Storage.FileProperties.ThumbnailOptions) +M:Windows.Storage.StorageFolder.TryGetItemAsync(System.String) +M:Windows.Storage.StorageLibrary.RequestAddFolderAsync +M:Windows.System.AppDiagnosticInfo.LaunchAsync +M:Windows.System.AppUriHandlerRegistrationManager.TryGetRegistration(System.String) +M:Windows.System.DispatcherQueue.GetForCurrentThread +M:Windows.System.RemoteSystems.RemoteSystem.FindByHostNameAsync(Windows.Networking.HostName) +M:Windows.System.User.GetFromId(System.String) +M:Windows.System.User.GetPictureAsync(Windows.System.UserPictureSize) +M:Windows.System.User.GetUserAgeRangeAsync +M:Windows.System.UserPicker.PickSingleUserAsync +M:Windows.System.UserProfile.UserInformation.GetAccountPicture(Windows.System.UserProfile.AccountPictureKind) +M:Windows.System.UserProfile.UserInformation.GetSessionInitiationProtocolUriAsync +M:Windows.UI.Composition.CompositionObject.TryGetAnimationController(System.String) +M:Windows.UI.Composition.Diagnostics.CompositionDebugSettings.TryGetSettings(Windows.UI.Composition.Compositor) +M:Windows.UI.Core.CoreWindow.GetForCurrentThread +M:Windows.UI.Core.CoreWindow.GetKeyState(Windows.System.VirtualKey) +M:Windows.UI.Input.Preview.Injection.InputInjector.TryCreate +M:Windows.UI.Input.Preview.Injection.InputInjector.TryCreateForAppBroadcastOnly +M:Windows.UI.Input.RadialControllerMenu.GetSelectedMenuItem +M:Windows.UI.Input.Spatial.SpatialHoldStartedEventArgs.TryGetPointerPose(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Input.Spatial.SpatialInteractionController.TryGetBatteryReport +M:Windows.UI.Input.Spatial.SpatialInteractionController.TryGetRenderableModelAsync +M:Windows.UI.Input.Spatial.SpatialInteractionDetectedEventArgs.TryGetPointerPose(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Input.Spatial.SpatialInteractionSource.TryCreateHandMeshObserver +M:Windows.UI.Input.Spatial.SpatialInteractionSource.TryCreateHandMeshObserverAsync +M:Windows.UI.Input.Spatial.SpatialInteractionSourceProperties.TryGetLocation(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Input.Spatial.SpatialInteractionSourceProperties.TryGetSourceLossMitigationDirection(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Input.Spatial.SpatialInteractionSourceState.TryGetHandPose +M:Windows.UI.Input.Spatial.SpatialInteractionSourceState.TryGetPointerPose(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Input.Spatial.SpatialManipulationCompletedEventArgs.TryGetCumulativeDelta(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Input.Spatial.SpatialManipulationStartedEventArgs.TryGetPointerPose(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Input.Spatial.SpatialManipulationUpdatedEventArgs.TryGetCumulativeDelta(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Input.Spatial.SpatialNavigationStartedEventArgs.TryGetPointerPose(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Input.Spatial.SpatialPointerPose.TryGetAtTimestamp(Windows.Perception.Spatial.SpatialCoordinateSystem,Windows.Perception.PerceptionTimestamp) +M:Windows.UI.Input.Spatial.SpatialPointerPose.TryGetInteractionSourcePose(Windows.UI.Input.Spatial.SpatialInteractionSource) +M:Windows.UI.Input.Spatial.SpatialRecognitionStartedEventArgs.TryGetPointerPose(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Input.Spatial.SpatialTappedEventArgs.TryGetPointerPose(Windows.Perception.Spatial.SpatialCoordinateSystem) +M:Windows.UI.Notifications.Management.UserNotificationListener.GetNotification(System.UInt32) +M:Windows.UI.Notifications.NotificationVisual.GetBinding(System.String) +M:Windows.UI.Popups.PopupMenu.ShowAsync(Windows.Foundation.Point) +M:Windows.UI.Popups.PopupMenu.ShowForSelectionAsync(Windows.Foundation.Rect) +M:Windows.UI.Popups.PopupMenu.ShowForSelectionAsync(Windows.Foundation.Rect,Windows.UI.Popups.Placement) +M:Windows.UI.Shell.FocusSessionManager.TryStartFocusSession +M:Windows.UI.Shell.FocusSessionManager.TryStartFocusSession(Windows.Foundation.DateTime) +M:Windows.UI.Text.ITextRange.InRange(Windows.UI.Text.ITextRange) +M:Windows.UI.Text.ITextRange.InStory(Windows.UI.Text.ITextRange) +M:Windows.UI.Text.ITextRange.MoveStart(Windows.UI.Text.TextRangeUnit,System.Int32) +M:Windows.UI.Text.RichEditTextRange.InRange(Windows.UI.Text.ITextRange) +M:Windows.UI.Text.RichEditTextRange.InStory(Windows.UI.Text.ITextRange) +M:Windows.UI.Text.RichEditTextRange.MoveStart(Windows.UI.Text.TextRangeUnit,System.Int32) +M:Windows.UI.UIAutomation.Core.AutomationRemoteOperationResult.GetOperand(Windows.UI.UIAutomation.Core.AutomationRemoteOperationOperandId) +M:Windows.UI.Xaml.Automation.Peers.AutomationPeer.GetPattern(Windows.UI.Xaml.Automation.Peers.PatternInterface) +M:Windows.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer.CreatePeerForElement(Windows.UI.Xaml.UIElement) +M:Windows.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer.FromElement(Windows.UI.Xaml.UIElement) +M:Windows.UI.Xaml.Automation.Peers.ItemsControlAutomationPeer.FindItemByProperty(Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple,Windows.UI.Xaml.Automation.AutomationProperty,System.Object) +M:Windows.UI.Xaml.Automation.Peers.LoopingSelectorAutomationPeer.FindItemByProperty(Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple,Windows.UI.Xaml.Automation.AutomationProperty,System.Object) +M:Windows.UI.Xaml.Automation.Provider.IDragProvider.GetGrabbedItems +M:Windows.UI.Xaml.Automation.Provider.IItemContainerProvider.FindItemByProperty(Windows.UI.Xaml.Automation.Provider.IRawElementProviderSimple,Windows.UI.Xaml.Automation.AutomationProperty,System.Object) +M:Windows.UI.Xaml.Automation.Provider.ITextRangeProvider.FindAttribute(System.Int32,System.Object,System.Boolean) +M:Windows.UI.Xaml.Automation.Provider.ITextRangeProvider.FindText(System.String,System.Boolean,System.Boolean) +M:Windows.UI.Xaml.Controls.Control.GetTemplateChild(System.String) +M:Windows.UI.Xaml.Controls.DataTemplateSelector.GetElement(Windows.UI.Xaml.ElementFactoryGetArgs) +M:Windows.UI.Xaml.Controls.DatePickerFlyoutItem.GetIndexedProperty(System.String,Windows.UI.Xaml.Interop.TypeName) +M:Windows.UI.Xaml.Controls.IItemContainerMapping.ContainerFromIndex(System.Int32) +M:Windows.UI.Xaml.Controls.IItemContainerMapping.ContainerFromItem(System.Object) +M:Windows.UI.Xaml.Controls.InkToolbar.GetToolButton(Windows.UI.Xaml.Controls.InkToolbarTool) +M:Windows.UI.Xaml.Controls.ItemContainerGenerator.ContainerFromIndex(System.Int32) +M:Windows.UI.Xaml.Controls.ItemContainerGenerator.ContainerFromItem(System.Object) +M:Windows.UI.Xaml.Controls.ItemsControl.ContainerFromIndex(System.Int32) +M:Windows.UI.Xaml.Controls.ItemsControl.ContainerFromItem(System.Object) +M:Windows.UI.Xaml.Controls.ItemsControl.GetItemsOwner(Windows.UI.Xaml.DependencyObject) +M:Windows.UI.Xaml.Controls.ItemsControl.ItemsControlFromItemContainer(Windows.UI.Xaml.DependencyObject) +M:Windows.UI.Xaml.Controls.Maps.MapControl.GetVisibleRegion(Windows.UI.Xaml.Controls.Maps.MapVisibleRegionKind) +M:Windows.UI.Xaml.Controls.Maps.StreetsidePanorama.FindNearbyAsync(Windows.Devices.Geolocation.Geopoint) +M:Windows.UI.Xaml.Controls.Maps.StreetsidePanorama.FindNearbyAsync(Windows.Devices.Geolocation.Geopoint,System.Double) +M:Windows.UI.Xaml.Controls.NavigationView.ContainerFromMenuItem(System.Object) +M:Windows.UI.Xaml.Controls.StyleSelector.SelectStyle(System.Object,Windows.UI.Xaml.DependencyObject) +M:Windows.UI.Xaml.Controls.SwapChainPanel.CreateCoreIndependentInputSource(Windows.UI.Core.CoreInputDeviceTypes) +M:Windows.UI.Xaml.Controls.TreeView.ContainerFromItem(System.Object) +M:Windows.UI.Xaml.Controls.TreeView.ContainerFromNode(Windows.UI.Xaml.Controls.TreeViewNode) +M:Windows.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(System.String) +M:Windows.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(System.String,Windows.UI.Xaml.Interop.TypeName) +M:Windows.UI.Xaml.DataTemplate.GetElement(Windows.UI.Xaml.ElementFactoryGetArgs) +M:Windows.UI.Xaml.Documents.TextElement.FindName(System.String) +M:Windows.UI.Xaml.Documents.TextPointer.GetPositionAtOffset(System.Int32,Windows.UI.Xaml.Documents.LogicalDirection) +M:Windows.UI.Xaml.FrameworkElement.FindName(System.String) +M:Windows.UI.Xaml.FrameworkElement.GetBindingExpression(Windows.UI.Xaml.DependencyProperty) +M:Windows.UI.Xaml.Input.FocusManager.FindNextFocusableElement(Windows.UI.Xaml.Input.FocusNavigationDirection) +M:Windows.UI.Xaml.Input.FocusManager.FindNextFocusableElement(Windows.UI.Xaml.Input.FocusNavigationDirection,Windows.Foundation.Rect) +M:Windows.UI.Xaml.Input.FocusManager.GetFocusedElement +M:Windows.UI.Xaml.Input.PointerRoutedEventArgs.GetIntermediatePoints(Windows.UI.Xaml.UIElement) +M:Windows.UI.Xaml.Markup.IXamlType.GetMember(System.String) +M:Windows.UI.Xaml.Media.Animation.ConnectedAnimationService.GetAnimation(System.String) +M:Windows.UI.Xaml.Media.Animation.Storyboard.GetCurrentTime +M:Windows.Web.IUriToStreamResolver.UriToStreamAsync(Windows.Foundation.Uri) +P:Windows.ApplicationModel.Activation.AppointmentsProviderAddAppointmentActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.AppointmentsProviderRemoveAppointmentActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.AppointmentsProviderReplaceAppointmentActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.AppointmentsProviderShowAppointmentDetailsActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.AppointmentsProviderShowTimeFrameActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.CachedFileUpdaterActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.DeviceActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.DeviceActivatedEventArgs.ViewSwitcher +P:Windows.ApplicationModel.Activation.DialReceiverActivatedEventArgs.ViewSwitcher +P:Windows.ApplicationModel.Activation.FileActivatedEventArgs.NeighboringFilesQuery +P:Windows.ApplicationModel.Activation.FileActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.FileActivatedEventArgs.ViewSwitcher +P:Windows.ApplicationModel.Activation.FileOpenPickerActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.FileOpenPickerContinuationEventArgs.User +P:Windows.ApplicationModel.Activation.FileSavePickerActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.FileSavePickerContinuationEventArgs.User +P:Windows.ApplicationModel.Activation.FolderPickerContinuationEventArgs.User +P:Windows.ApplicationModel.Activation.IViewSwitcherProvider.ViewSwitcher +P:Windows.ApplicationModel.Activation.LaunchActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.LockScreenCallActivatedEventArgs.ViewSwitcher +P:Windows.ApplicationModel.Activation.ProtocolActivatedEventArgs.Data +P:Windows.ApplicationModel.Activation.ProtocolActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.ProtocolActivatedEventArgs.ViewSwitcher +P:Windows.ApplicationModel.Activation.ProtocolForResultsActivatedEventArgs.Data +P:Windows.ApplicationModel.Activation.ProtocolForResultsActivatedEventArgs.ViewSwitcher +P:Windows.ApplicationModel.Activation.RestrictedLaunchActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.SearchActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.SearchActivatedEventArgs.ViewSwitcher +P:Windows.ApplicationModel.Activation.ShareTargetActivatedEventArgs.User +P:Windows.ApplicationModel.Activation.VoiceCommandActivatedEventArgs.User +P:Windows.ApplicationModel.AppInstallerInfo.PausedUntil +P:Windows.ApplicationModel.AppInstance.RecommendedInstance +P:Windows.ApplicationModel.Appointments.Appointment.Reminder +P:Windows.ApplicationModel.Background.BackgroundTaskRegistration.Trigger +P:Windows.ApplicationModel.Background.BluetoothLEAdvertisementPublisherTrigger.PreferredTransmitPowerLevelInDBm +P:Windows.ApplicationModel.Background.IBackgroundTaskRegistration2.Trigger +P:Windows.ApplicationModel.Background.RfcommConnectionTrigger.InboundConnection +P:Windows.ApplicationModel.Background.RfcommConnectionTrigger.OutboundConnection +P:Windows.ApplicationModel.Calls.PhoneLineDialResult.DialedCall +P:Windows.ApplicationModel.Package.MachineExternalLocation +P:Windows.ApplicationModel.Package.UserExternalLocation +P:Windows.ApplicationModel.PackageCatalogAddOptionalPackageResult.ExtendedError +P:Windows.ApplicationModel.PackageCatalogAddOptionalPackageResult.Package +P:Windows.ApplicationModel.PackageId.ResourceId +P:Windows.ApplicationModel.Search.Core.SearchSuggestion.DetailText +P:Windows.ApplicationModel.Search.Core.SearchSuggestion.Image +P:Windows.ApplicationModel.Search.Core.SearchSuggestion.ImageAlternateText +P:Windows.ApplicationModel.Search.Core.SearchSuggestion.Tag +P:Windows.ApplicationModel.UserDataAccounts.UserDataAccount.EnterpriseId +P:Windows.ApplicationModel.UserDataAccounts.UserDataAccount.Icon +P:Windows.ApplicationModel.UserDataTasks.UserDataTask.Reminder +P:Windows.ApplicationModel.Wallet.WalletItem.ExpirationDate +P:Windows.ApplicationModel.Wallet.WalletItem.LastUpdated +P:Windows.ApplicationModel.Wallet.WalletItem.RelevantDate +P:Windows.ApplicationModel.Wallet.WalletTransaction.TransactionDate +P:Windows.Data.Xml.Dom.DtdEntity.FirstChild +P:Windows.Data.Xml.Dom.DtdEntity.LastChild +P:Windows.Data.Xml.Dom.DtdEntity.NextSibling +P:Windows.Data.Xml.Dom.DtdEntity.NodeValue +P:Windows.Data.Xml.Dom.DtdNotation.FirstChild +P:Windows.Data.Xml.Dom.DtdNotation.LastChild +P:Windows.Data.Xml.Dom.DtdNotation.NextSibling +P:Windows.Data.Xml.Dom.DtdNotation.NodeValue +P:Windows.Data.Xml.Dom.IXmlNode.Attributes +P:Windows.Data.Xml.Dom.IXmlNode.FirstChild +P:Windows.Data.Xml.Dom.IXmlNode.LastChild +P:Windows.Data.Xml.Dom.XmlAttribute.FirstChild +P:Windows.Data.Xml.Dom.XmlAttribute.LastChild +P:Windows.Data.Xml.Dom.XmlAttribute.NextSibling +P:Windows.Data.Xml.Dom.XmlCDataSection.ChildNodes +P:Windows.Data.Xml.Dom.XmlCDataSection.FirstChild +P:Windows.Data.Xml.Dom.XmlCDataSection.LastChild +P:Windows.Data.Xml.Dom.XmlComment.ChildNodes +P:Windows.Data.Xml.Dom.XmlComment.FirstChild +P:Windows.Data.Xml.Dom.XmlComment.LastChild +P:Windows.Data.Xml.Dom.XmlDocument.FirstChild +P:Windows.Data.Xml.Dom.XmlDocument.LastChild +P:Windows.Data.Xml.Dom.XmlDocument.ParentNode +P:Windows.Data.Xml.Dom.XmlDocumentFragment.Attributes +P:Windows.Data.Xml.Dom.XmlDocumentFragment.FirstChild +P:Windows.Data.Xml.Dom.XmlDocumentFragment.LastChild +P:Windows.Data.Xml.Dom.XmlDocumentType.Attributes +P:Windows.Data.Xml.Dom.XmlDocumentType.FirstChild +P:Windows.Data.Xml.Dom.XmlDocumentType.LastChild +P:Windows.Data.Xml.Dom.XmlElement.Attributes +P:Windows.Data.Xml.Dom.XmlElement.FirstChild +P:Windows.Data.Xml.Dom.XmlElement.LastChild +P:Windows.Data.Xml.Dom.XmlEntityReference.Attributes +P:Windows.Data.Xml.Dom.XmlEntityReference.FirstChild +P:Windows.Data.Xml.Dom.XmlEntityReference.LastChild +P:Windows.Data.Xml.Dom.XmlProcessingInstruction.Attributes +P:Windows.Data.Xml.Dom.XmlProcessingInstruction.FirstChild +P:Windows.Data.Xml.Dom.XmlProcessingInstruction.LastChild +P:Windows.Data.Xml.Dom.XmlText.Attributes +P:Windows.Data.Xml.Dom.XmlText.FirstChild +P:Windows.Data.Xml.Dom.XmlText.LastChild +P:Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementPublisher.PreferredTransmitPowerLevelInDBm +P:Windows.Devices.Bluetooth.GenericAttributeProfile.GattDeviceService.ParentServices +P:Windows.Devices.Display.Core.DisplayTarget.DeviceInterfacePath +P:Windows.Devices.Display.DisplayMonitor.BluePrimary +P:Windows.Devices.Display.DisplayMonitor.DeviceId +P:Windows.Devices.Display.DisplayMonitor.DisplayAdapterDeviceId +P:Windows.Devices.Display.DisplayMonitor.DisplayAdapterId +P:Windows.Devices.Display.DisplayMonitor.DisplayName +P:Windows.Devices.Display.DisplayMonitor.GreenPrimary +P:Windows.Devices.Display.DisplayMonitor.PhysicalSizeInInches +P:Windows.Devices.Display.DisplayMonitor.RedPrimary +P:Windows.Devices.Display.DisplayMonitor.WhitePoint +P:Windows.Devices.Enumeration.DeviceInformation.EnclosureLocation +P:Windows.Devices.Geolocation.GeocoordinateSatelliteData.GeometricDilutionOfPrecision +P:Windows.Devices.Geolocation.GeocoordinateSatelliteData.TimeDilutionOfPrecision +P:Windows.Devices.Geolocation.Geoposition.CivicAddress +P:Windows.Devices.Geolocation.Geoposition.VenueData +P:Windows.Devices.Haptics.InputHapticsManager.CurrentHapticsController +P:Windows.Devices.PointOfService.BarcodeScannerReport.ScanDataLabel +P:Windows.Devices.PointOfService.CashDrawer.DrawerEventSource +P:Windows.Devices.PointOfService.ClaimedLineDisplay.CustomGlyphs +P:Windows.Devices.PointOfService.ClaimedPosPrinter.Journal +P:Windows.Devices.PointOfService.ClaimedPosPrinter.Receipt +P:Windows.Devices.PointOfService.ClaimedPosPrinter.Slip +P:Windows.Devices.Scanners.ImageScanner.AutoConfiguration +P:Windows.Devices.Scanners.ImageScanner.FeederConfiguration +P:Windows.Devices.Scanners.ImageScanner.FlatbedConfiguration +P:Windows.Devices.Sensors.AccelerometerReading.PerformanceCount +P:Windows.Devices.Sensors.AltimeterReading.PerformanceCount +P:Windows.Devices.Sensors.BarometerReading.PerformanceCount +P:Windows.Devices.Sensors.CompassReading.PerformanceCount +P:Windows.Devices.Sensors.Custom.CustomSensorReading.PerformanceCount +P:Windows.Devices.Sensors.GyrometerReading.PerformanceCount +P:Windows.Devices.Sensors.InclinometerReading.PerformanceCount +P:Windows.Devices.Sensors.LightSensorReading.PerformanceCount +P:Windows.Devices.Sensors.MagnetometerReading.PerformanceCount +P:Windows.Devices.Sensors.OrientationSensorReading.PerformanceCount +P:Windows.Devices.SmartCards.SmartCardCryptogramPlacementStep.Algorithm +P:Windows.Devices.Usb.UsbBulkInEndpointDescriptor.Pipe +P:Windows.Devices.WiFi.WiFiOnDemandHotspotNetworkProperties.RemainingBatteryPercent +P:Windows.Devices.WiFiDirect.Services.WiFiDirectServiceAutoAcceptSessionConnectedEventArgs.SessionInfo +P:Windows.Devices.WiFiDirect.WiFiDirectAdvertisement.InformationElements +P:Windows.Gaming.Input.RacingWheel.WheelMotor +P:Windows.Globalization.Fonts.LanguageFontGroup.DocumentAlternate1Font +P:Windows.Globalization.Fonts.LanguageFontGroup.DocumentAlternate2Font +P:Windows.Globalization.Fonts.LanguageFontGroup.FixedWidthTextFont +P:Windows.Graphics.Display.DisplayEnhancementOverride.BrightnessOverrideSettings +P:Windows.Graphics.Display.DisplayEnhancementOverride.ColorOverrideSettings +P:Windows.Graphics.Display.DisplayInformation.DiagonalSizeInInches +P:Windows.Media.Audio.AudioFileInputNode.EndTime +P:Windows.Media.Audio.AudioFileInputNode.StartTime +P:Windows.Media.Audio.AudioGraphSettings.PrimaryRenderDevice +P:Windows.Media.Audio.MediaSourceAudioInputNode.EndTime +P:Windows.Media.Audio.MediaSourceAudioInputNode.StartTime +P:Windows.Media.Capture.AdvancedCapturedPhoto.FrameBoundsRelativeToReferencePhoto +P:Windows.Media.Capture.Frames.MediaFrameFormat.VideoFormat +P:Windows.Media.Capture.Frames.MediaFrameReference.AudioMediaFrame +P:Windows.Media.Capture.Frames.MediaFrameReference.BufferMediaFrame +P:Windows.Media.Capture.Frames.MediaFrameReference.VideoMediaFrame +P:Windows.Media.Capture.Frames.MediaFrameSourceGetPropertyResult.Value +P:Windows.Media.Capture.Frames.VideoMediaFrame.SoftwareBitmap +P:Windows.Media.Capture.Frames.VideoMediaFrameFormat.DepthFormat +P:Windows.Media.Capture.MediaCaptureInitializationSettings.AudioDeviceId +P:Windows.Media.Capture.MediaCaptureInitializationSettings.VideoDeviceId +P:Windows.Media.Capture.MediaCaptureSettings.AudioDeviceId +P:Windows.Media.Capture.MediaCaptureSettings.VideoDeviceId +P:Windows.Media.Core.MediaSource.AdaptiveMediaSource +P:Windows.Media.Core.MediaSource.MediaStreamSource +P:Windows.Media.Core.MediaSource.MseStreamSource +P:Windows.Media.Core.MediaStreamSourceSampleRequest.Sample +P:Windows.Media.Core.MediaStreamSourceStartingRequest.StartPosition +P:Windows.Media.Devices.VideoDeviceControllerGetDevicePropertyResult.Value +P:Windows.Media.Effects.AudioEffect.AcousticEchoCancellationConfiguration +P:Windows.Media.MediaProperties.MediaEncodingProfile.Audio +P:Windows.Media.MediaProperties.MediaEncodingProfile.Video +P:Windows.Media.Ocr.OcrResult.TextAngle +P:Windows.Media.Protection.PlayReady.PlayReadyContentHeader.HeaderWithEmbeddedUpdates +P:Windows.Media.Protection.PlayReady.PlayReadyIndividualizationServiceRequest.ChallengeCustomData +P:Windows.Media.Protection.PlayReady.PlayReadyIndividualizationServiceRequest.ResponseCustomData +P:Windows.Media.Protection.PlayReady.PlayReadyIndividualizationServiceRequest.Uri +P:Windows.Media.Protection.PlayReady.PlayReadyRevocationServiceRequest.ChallengeCustomData +P:Windows.Media.Protection.PlayReady.PlayReadyRevocationServiceRequest.ResponseCustomData +P:Windows.Media.Protection.PlayReady.PlayReadyRevocationServiceRequest.Uri +P:Windows.Media.Protection.PlayReady.PlayReadyStatics.HardwareDRMDisabledAtTime +P:Windows.Media.Protection.PlayReady.PlayReadyStatics.HardwareDRMDisabledUntilTime +P:Windows.Media.SpeechRecognition.SpeechRecognitionResult.Constraint +P:Windows.Media.SpeechRecognition.SpeechRecognizer.SystemSpeechLanguage +P:Windows.Media.Streaming.Adaptive.AdaptiveMediaSourceCorrelatedTimes.PresentationTimeStamp +P:Windows.Media.VideoFrame.Direct3DSurface +P:Windows.Media.VideoFrame.SoftwareBitmap +P:Windows.Networking.BackgroundTransfer.BackgroundDownloader.CompletionGroup +P:Windows.Networking.BackgroundTransfer.BackgroundUploader.CompletionGroup +P:Windows.Networking.EndpointPair.LocalHostName +P:Windows.Networking.HostName.IPInformation +P:Windows.Networking.NetworkOperators.ESim.SlotIndex +P:Windows.Networking.NetworkOperators.ESimDownloadProfileMetadataResult.ProfileMetadata +P:Windows.Networking.NetworkOperators.NetworkOperatorNotificationEventDetails.SmsMessage +P:Windows.Networking.NetworkOperators.UssdReply.Message +P:Windows.Networking.Proximity.TriggeredConnectionStateChangedEventArgs.Socket +P:Windows.Networking.PushNotifications.PushNotificationReceivedEventArgs.BadgeNotification +P:Windows.Networking.PushNotifications.PushNotificationReceivedEventArgs.RawNotification +P:Windows.Networking.PushNotifications.PushNotificationReceivedEventArgs.TileNotification +P:Windows.Networking.PushNotifications.PushNotificationReceivedEventArgs.ToastNotification +P:Windows.Networking.Sockets.IWebSocketInformation.Protocol +P:Windows.Networking.Sockets.MessageWebSocketInformation.Protocol +P:Windows.Networking.Sockets.StreamSocketInformation.RemoteAddress +P:Windows.Networking.Sockets.StreamSocketInformation.SessionKey +P:Windows.Networking.Sockets.StreamWebSocketInformation.Protocol +P:Windows.Networking.Vpn.VpnChannel.CurrentRequestTransportContext +P:Windows.Perception.People.EyesPose.Gaze +P:Windows.Perception.Spatial.SpatialStageFrameOfReference.Current +P:Windows.Perception.Spatial.Surfaces.SpatialSurfaceMesh.VertexNormals +P:Windows.Security.EnterpriseData.ProtectionPolicyManager.PrimaryManagedIdentity +P:Windows.Services.Cortana.CortanaActionableInsights.User +P:Windows.Services.Cortana.CortanaActionableInsightsOptions.ContentSourceWebLink +P:Windows.Services.Cortana.CortanaActionableInsightsOptions.SurroundingText +P:Windows.Services.Maps.Guidance.GuidanceManeuver.RoadSignpost +P:Windows.Services.Maps.Guidance.GuidanceRoadSignpost.Exit +P:Windows.Services.Maps.Guidance.GuidanceRoadSignpost.ExitNumber +P:Windows.Services.Maps.MapRouteFinderResult.AlternateRoutes +P:Windows.Services.Store.StoreContext.User +P:Windows.Services.Store.StoreSku.SubscriptionInfo +P:Windows.System.RemoteSystems.RemoteSystemSessionCreationResult.Session +P:Windows.System.RemoteSystems.RemoteSystemSessionJoinResult.Session +P:Windows.UI.Composition.CompositionGeometricClip.Geometry +P:Windows.UI.Composition.RedirectVisual.Source +P:Windows.UI.Composition.ScalarNaturalMotionAnimation.FinalValue +P:Windows.UI.Composition.ScalarNaturalMotionAnimation.InitialValue +P:Windows.UI.Composition.Vector2NaturalMotionAnimation.FinalValue +P:Windows.UI.Composition.Vector2NaturalMotionAnimation.InitialValue +P:Windows.UI.Composition.Vector3NaturalMotionAnimation.FinalValue +P:Windows.UI.Composition.Vector3NaturalMotionAnimation.InitialValue +P:Windows.UI.Input.Inking.InkPresenter.StrokeContainer +P:Windows.UI.Input.PointerPointProperties.ZDistance +P:Windows.UI.Input.RadialControllerButtonClickedEventArgs.Contact +P:Windows.UI.Input.RadialControllerButtonHoldingEventArgs.Contact +P:Windows.UI.Input.RadialControllerButtonPressedEventArgs.Contact +P:Windows.UI.Input.RadialControllerButtonReleasedEventArgs.Contact +P:Windows.UI.Input.RadialControllerControlAcquiredEventArgs.Contact +P:Windows.UI.Input.RadialControllerRotationChangedEventArgs.Contact +P:Windows.UI.Input.RadialControllerScreenContactContinuedEventArgs.Contact +P:Windows.UI.Input.RadialControllerScreenContactStartedEventArgs.Contact +P:Windows.UI.Input.Spatial.SpatialInteractionController.SimpleHapticsController +P:Windows.UI.Input.Spatial.SpatialInteractionSource.Controller +P:Windows.UI.Input.Spatial.SpatialInteractionSourceLocation.SourcePointerPose +P:Windows.UI.Input.Spatial.SpatialInteractionSourceState.ControllerProperties +P:Windows.UI.Input.Spatial.SpatialPointerPose.Eyes +P:Windows.UI.Popups.UICommand.Invoked +P:Windows.UI.Shell.Tasks.AppTaskInfo.EndTime +P:Windows.UI.StartScreen.TileMixedRealityModel.BoundingBox +P:Windows.UI.Text.ContentLinkInfo.Uri +P:Windows.UI.Text.Core.CoreTextSelectionRequest.Selection +P:Windows.UI.Text.Core.CoreTextTextRequest.Text +P:Windows.UI.WebUI.WebUIAppointmentsProviderAddAppointmentActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIAppointmentsProviderRemoveAppointmentActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIAppointmentsProviderReplaceAppointmentActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIAppointmentsProviderShowAppointmentDetailsActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIAppointmentsProviderShowTimeFrameActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIBackgroundTaskInstance.Current +P:Windows.UI.WebUI.WebUICachedFileUpdaterActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIContactPanelActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIDeviceActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIDevicePairingActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIDialReceiverActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIFileActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIFileOpenPickerActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIFileOpenPickerContinuationEventArgs.User +P:Windows.UI.WebUI.WebUIFileSavePickerActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIFileSavePickerContinuationEventArgs.User +P:Windows.UI.WebUI.WebUIFolderPickerContinuationEventArgs.User +P:Windows.UI.WebUI.WebUILaunchActivatedEventArgs.User +P:Windows.UI.WebUI.WebUILockScreenActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIProtocolActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIProtocolForResultsActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIRestrictedLaunchActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIShareTargetActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIToastNotificationActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIVoiceCommandActivatedEventArgs.User +P:Windows.UI.WebUI.WebUIWebAccountProviderActivatedEventArgs.User +P:Windows.UI.Xaml.Automation.Provider.IDragProvider.DropEffect +P:Windows.UI.Xaml.Automation.Provider.IRangeValueProvider.LargeChange +P:Windows.UI.Xaml.Automation.Provider.IRangeValueProvider.Maximum +P:Windows.UI.Xaml.Automation.Provider.IRangeValueProvider.Minimum +P:Windows.UI.Xaml.Automation.Provider.IRangeValueProvider.SmallChange +P:Windows.UI.Xaml.Automation.Provider.IRangeValueProvider.Value +P:Windows.UI.Xaml.BringIntoViewOptions.TargetRect +P:Windows.UI.Xaml.Controls.AnchorRequestedEventArgs.Anchor +P:Windows.UI.Xaml.Controls.AppBarButton.KeyboardAcceleratorTextOverride +P:Windows.UI.Xaml.Controls.AppBarToggleButton.KeyboardAcceleratorTextOverride +P:Windows.UI.Xaml.Controls.AutoSuggestBox.Description +P:Windows.UI.Xaml.Controls.AutoSuggestBox.QueryIcon +P:Windows.UI.Xaml.Controls.BitmapIcon.UriSource +P:Windows.UI.Xaml.Controls.BitmapIconSource.UriSource +P:Windows.UI.Xaml.Controls.Border.Background +P:Windows.UI.Xaml.Controls.Border.BackgroundTransition +P:Windows.UI.Xaml.Controls.Border.BorderBrush +P:Windows.UI.Xaml.Controls.Button.Flyout +P:Windows.UI.Xaml.Controls.CalendarDatePicker.Description +P:Windows.UI.Xaml.Controls.CalendarDatePicker.Header +P:Windows.UI.Xaml.Controls.CalendarDatePicker.HeaderTemplate +P:Windows.UI.Xaml.Controls.CalendarViewDayItemChangingEventArgs.Item +P:Windows.UI.Xaml.Controls.ColorPicker.PreviousColor +P:Windows.UI.Xaml.Controls.ComboBox.Description +P:Windows.UI.Xaml.Controls.ComboBox.Header +P:Windows.UI.Xaml.Controls.ComboBox.HeaderTemplate +P:Windows.UI.Xaml.Controls.CommandBar.CommandBarOverflowPresenterStyle +P:Windows.UI.Xaml.Controls.ContainerContentChangingEventArgs.Item +P:Windows.UI.Xaml.Controls.ContentControl.Content +P:Windows.UI.Xaml.Controls.ContentControl.ContentTemplateRoot +P:Windows.UI.Xaml.Controls.ContentDialog.CloseButtonCommandParameter +P:Windows.UI.Xaml.Controls.ContentDialog.CloseButtonStyle +P:Windows.UI.Xaml.Controls.ContentDialog.PrimaryButtonCommandParameter +P:Windows.UI.Xaml.Controls.ContentDialog.PrimaryButtonStyle +P:Windows.UI.Xaml.Controls.ContentDialog.PrimaryButtonText +P:Windows.UI.Xaml.Controls.ContentDialog.SecondaryButtonCommandParameter +P:Windows.UI.Xaml.Controls.ContentDialog.SecondaryButtonStyle +P:Windows.UI.Xaml.Controls.ContentDialog.SecondaryButtonText +P:Windows.UI.Xaml.Controls.ContentPresenter.Background +P:Windows.UI.Xaml.Controls.ContentPresenter.BackgroundTransition +P:Windows.UI.Xaml.Controls.ContentPresenter.BorderBrush +P:Windows.UI.Xaml.Controls.ContentPresenter.Content +P:Windows.UI.Xaml.Controls.ContentPresenter.ContentTemplate +P:Windows.UI.Xaml.Controls.ContentPresenter.Foreground +P:Windows.UI.Xaml.Controls.Control.Background +P:Windows.UI.Xaml.Controls.Control.BorderBrush +P:Windows.UI.Xaml.Controls.DatePicker.Header +P:Windows.UI.Xaml.Controls.DatePicker.HeaderTemplate +P:Windows.UI.Xaml.Controls.DatePicker.SelectedDate +P:Windows.UI.Xaml.Controls.Flyout.FlyoutPresenterStyle +P:Windows.UI.Xaml.Controls.Grid.BorderBrush +P:Windows.UI.Xaml.Controls.GroupStyle.ContainerStyle +P:Windows.UI.Xaml.Controls.GroupStyle.ContainerStyleSelector +P:Windows.UI.Xaml.Controls.GroupStyle.HeaderContainerStyle +P:Windows.UI.Xaml.Controls.GroupStyle.HeaderTemplate +P:Windows.UI.Xaml.Controls.GroupStyle.HeaderTemplateSelector +P:Windows.UI.Xaml.Controls.HandwritingView.PlacementTarget +P:Windows.UI.Xaml.Controls.Hub.Header +P:Windows.UI.Xaml.Controls.Hub.HeaderTemplate +P:Windows.UI.Xaml.Controls.Hub.SemanticZoomOwner +P:Windows.UI.Xaml.Controls.HubSection.Header +P:Windows.UI.Xaml.Controls.HubSection.HeaderTemplate +P:Windows.UI.Xaml.Controls.IScrollAnchorProvider.CurrentAnchor +P:Windows.UI.Xaml.Controls.IconElement.Foreground +P:Windows.UI.Xaml.Controls.IconSource.Foreground +P:Windows.UI.Xaml.Controls.IconSourceElement.IconSource +P:Windows.UI.Xaml.Controls.InkToolbarCustomPenButton.ConfigurationContent +P:Windows.UI.Xaml.Controls.InkToolbarCustomToolButton.ConfigurationContent +P:Windows.UI.Xaml.Controls.ItemsControl.ItemContainerStyle +P:Windows.UI.Xaml.Controls.ItemsControl.ItemTemplate +P:Windows.UI.Xaml.Controls.ItemsControl.Items +P:Windows.UI.Xaml.Controls.ItemsControl.ItemsPanelRoot +P:Windows.UI.Xaml.Controls.ItemsControl.ItemsSource +P:Windows.UI.Xaml.Controls.ItemsPresenter.Footer +P:Windows.UI.Xaml.Controls.ItemsPresenter.FooterTemplate +P:Windows.UI.Xaml.Controls.ItemsPresenter.Header +P:Windows.UI.Xaml.Controls.ItemsPresenter.HeaderTemplate +P:Windows.UI.Xaml.Controls.ListPickerFlyout.ItemTemplate +P:Windows.UI.Xaml.Controls.ListPickerFlyout.ItemsSource +P:Windows.UI.Xaml.Controls.ListPickerFlyout.SelectedItem +P:Windows.UI.Xaml.Controls.ListPickerFlyout.SelectedValue +P:Windows.UI.Xaml.Controls.ListViewBase.Footer +P:Windows.UI.Xaml.Controls.ListViewBase.FooterTemplate +P:Windows.UI.Xaml.Controls.ListViewBase.Header +P:Windows.UI.Xaml.Controls.ListViewBase.HeaderTemplate +P:Windows.UI.Xaml.Controls.ListViewBase.SemanticZoomOwner +P:Windows.UI.Xaml.Controls.Maps.MapControl.Region +P:Windows.UI.Xaml.Controls.MediaElement.AudioStreamIndex +P:Windows.UI.Xaml.Controls.MediaElement.Source +P:Windows.UI.Xaml.Controls.MediaPlayerElement.Source +P:Windows.UI.Xaml.Controls.MenuFlyoutItem.Command +P:Windows.UI.Xaml.Controls.MenuFlyoutItem.CommandParameter +P:Windows.UI.Xaml.Controls.MenuFlyoutItem.KeyboardAcceleratorTextOverride +P:Windows.UI.Xaml.Controls.MenuFlyoutSubItem.Items +P:Windows.UI.Xaml.Controls.NavigationView.MenuItemContainerStyle +P:Windows.UI.Xaml.Controls.NavigationView.MenuItemTemplate +P:Windows.UI.Xaml.Controls.NavigationView.MenuItemsSource +P:Windows.UI.Xaml.Controls.NavigationView.PaneFooter +P:Windows.UI.Xaml.Controls.NavigationView.PaneHeader +P:Windows.UI.Xaml.Controls.NavigationView.PaneToggleButtonStyle +P:Windows.UI.Xaml.Controls.NavigationView.SelectedItem +P:Windows.UI.Xaml.Controls.NavigationViewItem.Icon +P:Windows.UI.Xaml.Controls.Page.BottomAppBar +P:Windows.UI.Xaml.Controls.Page.TopAppBar +P:Windows.UI.Xaml.Controls.Panel.Background +P:Windows.UI.Xaml.Controls.Panel.BackgroundTransition +P:Windows.UI.Xaml.Controls.ParallaxView.Child +P:Windows.UI.Xaml.Controls.PasswordBox.Description +P:Windows.UI.Xaml.Controls.PasswordBox.Header +P:Windows.UI.Xaml.Controls.PasswordBox.HeaderTemplate +P:Windows.UI.Xaml.Controls.PasswordBox.InputScope +P:Windows.UI.Xaml.Controls.PasswordBox.SelectionFlyout +P:Windows.UI.Xaml.Controls.PasswordBox.SelectionHighlightColor +P:Windows.UI.Xaml.Controls.Pivot.LeftHeader +P:Windows.UI.Xaml.Controls.Pivot.RightHeader +P:Windows.UI.Xaml.Controls.Primitives.ButtonBase.Command +P:Windows.UI.Xaml.Controls.Primitives.ButtonBase.CommandParameter +P:Windows.UI.Xaml.Controls.Primitives.FlyoutBase.XamlRoot +P:Windows.UI.Xaml.Controls.Primitives.Selector.SelectedItem +P:Windows.UI.Xaml.Controls.Primitives.Selector.SelectedValue +P:Windows.UI.Xaml.Controls.RadioButton.GroupName +P:Windows.UI.Xaml.Controls.RatingControl.PlaceholderValue +P:Windows.UI.Xaml.Controls.RatingControl.Value +P:Windows.UI.Xaml.Controls.RelativePanel.BorderBrush +P:Windows.UI.Xaml.Controls.RichEditBox.Description +P:Windows.UI.Xaml.Controls.RichEditBox.Header +P:Windows.UI.Xaml.Controls.RichEditBox.HeaderTemplate +P:Windows.UI.Xaml.Controls.RichEditBox.InputScope +P:Windows.UI.Xaml.Controls.RichEditBox.SelectionFlyout +P:Windows.UI.Xaml.Controls.RichEditBox.SelectionHighlightColor +P:Windows.UI.Xaml.Controls.RichEditBox.SelectionHighlightColorWhenNotFocused +P:Windows.UI.Xaml.Controls.RichTextBlock.Foreground +P:Windows.UI.Xaml.Controls.RichTextBlock.SelectionEnd +P:Windows.UI.Xaml.Controls.RichTextBlock.SelectionFlyout +P:Windows.UI.Xaml.Controls.RichTextBlock.SelectionHighlightColor +P:Windows.UI.Xaml.Controls.RichTextBlock.SelectionStart +P:Windows.UI.Xaml.Controls.ScrollViewer.CurrentAnchor +P:Windows.UI.Xaml.Controls.SettingsFlyout.HeaderBackground +P:Windows.UI.Xaml.Controls.SettingsFlyout.HeaderForeground +P:Windows.UI.Xaml.Controls.SettingsFlyout.IconSource +P:Windows.UI.Xaml.Controls.Slider.Header +P:Windows.UI.Xaml.Controls.Slider.HeaderTemplate +P:Windows.UI.Xaml.Controls.SplitButton.Command +P:Windows.UI.Xaml.Controls.SplitButton.CommandParameter +P:Windows.UI.Xaml.Controls.SplitButton.Flyout +P:Windows.UI.Xaml.Controls.SplitView.Content +P:Windows.UI.Xaml.Controls.SplitView.Pane +P:Windows.UI.Xaml.Controls.StackPanel.BorderBrush +P:Windows.UI.Xaml.Controls.SwipeItem.Command +P:Windows.UI.Xaml.Controls.SwipeItem.CommandParameter +P:Windows.UI.Xaml.Controls.SwipeItem.IconSource +P:Windows.UI.Xaml.Controls.TextBlock.Foreground +P:Windows.UI.Xaml.Controls.TextBlock.SelectionEnd +P:Windows.UI.Xaml.Controls.TextBlock.SelectionFlyout +P:Windows.UI.Xaml.Controls.TextBlock.SelectionHighlightColor +P:Windows.UI.Xaml.Controls.TextBlock.SelectionStart +P:Windows.UI.Xaml.Controls.TextBox.Description +P:Windows.UI.Xaml.Controls.TextBox.Header +P:Windows.UI.Xaml.Controls.TextBox.HeaderTemplate +P:Windows.UI.Xaml.Controls.TextBox.InputScope +P:Windows.UI.Xaml.Controls.TextBox.SelectionFlyout +P:Windows.UI.Xaml.Controls.TextBox.SelectionHighlightColorWhenNotFocused +P:Windows.UI.Xaml.Controls.TimePicker.Header +P:Windows.UI.Xaml.Controls.TimePicker.HeaderTemplate +P:Windows.UI.Xaml.Controls.TimePicker.SelectedTime +P:Windows.UI.Xaml.Controls.ToolTip.PlacementRect +P:Windows.UI.Xaml.Controls.ToolTip.PlacementTarget +P:Windows.UI.Xaml.Controls.TreeView.ItemContainerStyle +P:Windows.UI.Xaml.Controls.TreeView.ItemTemplate +P:Windows.UI.Xaml.Controls.TreeView.ItemsSource +P:Windows.UI.Xaml.Controls.TreeViewItem.ItemsSource +P:Windows.UI.Xaml.Data.Binding.ConverterParameter +P:Windows.UI.Xaml.Data.Binding.RelativeSource +P:Windows.UI.Xaml.Data.Binding.TargetNullValue +P:Windows.UI.Xaml.Data.ICollectionView.CurrentItem +P:Windows.UI.Xaml.Documents.Glyphs.Fill +P:Windows.UI.Xaml.Documents.Glyphs.FontUri +P:Windows.UI.Xaml.Documents.Hyperlink.NavigateUri +P:Windows.UI.Xaml.Documents.TextElement.XamlRoot +P:Windows.UI.Xaml.DragEventArgs.DragUIOverride +P:Windows.UI.Xaml.ElementFactoryGetArgs.Parent +P:Windows.UI.Xaml.ElementFactoryRecycleArgs.Parent +P:Windows.UI.Xaml.FrameworkElement.Parent +P:Windows.UI.Xaml.FrameworkElement.Style +P:Windows.UI.Xaml.Input.FindNextElementOptions.SearchRoot +P:Windows.UI.Xaml.Input.FocusManagerGotFocusEventArgs.CorrelationId +P:Windows.UI.Xaml.Input.FocusManagerLostFocusEventArgs.CorrelationId +P:Windows.UI.Xaml.Input.GettingFocusEventArgs.CorrelationId +P:Windows.UI.Xaml.Input.KeyboardAccelerator.ScopeOwner +P:Windows.UI.Xaml.Input.LosingFocusEventArgs.CorrelationId +P:Windows.UI.Xaml.Markup.IXamlType.ContentProperty +P:Windows.UI.Xaml.Markup.IXamlType.ItemType +P:Windows.UI.Xaml.Markup.IXamlType.KeyType +P:Windows.UI.Xaml.Media.Animation.BeginStoryboard.Storyboard +P:Windows.UI.Xaml.Media.Animation.ColorAnimation.By +P:Windows.UI.Xaml.Media.Animation.ColorAnimation.From +P:Windows.UI.Xaml.Media.Animation.ColorAnimation.To +P:Windows.UI.Xaml.Media.Animation.ColorKeyFrame.KeyTime +P:Windows.UI.Xaml.Media.Animation.DoubleAnimation.By +P:Windows.UI.Xaml.Media.Animation.DoubleAnimation.From +P:Windows.UI.Xaml.Media.Animation.DoubleAnimation.To +P:Windows.UI.Xaml.Media.Animation.DoubleKeyFrame.KeyTime +P:Windows.UI.Xaml.Media.Animation.ObjectKeyFrame.KeyTime +P:Windows.UI.Xaml.Media.Animation.ObjectKeyFrame.Value +P:Windows.UI.Xaml.Media.Animation.PointAnimation.By +P:Windows.UI.Xaml.Media.Animation.PointAnimation.EasingFunction +P:Windows.UI.Xaml.Media.Animation.PointAnimation.From +P:Windows.UI.Xaml.Media.Animation.PointAnimation.To +P:Windows.UI.Xaml.Media.Animation.PointKeyFrame.KeyTime +P:Windows.UI.Xaml.Media.Brush.RelativeTransform +P:Windows.UI.Xaml.Media.GeneralTransform.Inverse +P:Windows.UI.Xaml.Media.PlaneProjection.ProjectionMatrix +P:Windows.UI.Xaml.Media.RectangleGeometry.Rect +P:Windows.UI.Xaml.Media.TimelineMarker.Time +P:Windows.UI.Xaml.Navigation.NavigationEventArgs.Parameter +P:Windows.UI.Xaml.Setter.Property +P:Windows.UI.Xaml.Shapes.Polygon.Points +P:Windows.UI.Xaml.Shapes.Polyline.Points +P:Windows.UI.Xaml.Shapes.Shape.Fill +P:Windows.UI.Xaml.Shapes.Shape.Stroke +P:Windows.UI.Xaml.Style.BasedOn +P:Windows.UI.Xaml.UIElement.CacheMode +P:Windows.UI.Xaml.UIElement.Clip +P:Windows.UI.Xaml.UIElement.ContextFlyout +P:Windows.UI.Xaml.UIElement.RenderTransform +P:Windows.UI.Xaml.UIElement.Transform3D +P:Windows.UI.Xaml.UIElement.XamlRoot +P:Windows.UI.Xaml.VisualStateGroup.CurrentState +P:Windows.Web.Http.Headers.HttpContentHeaderCollection.ContentDisposition +P:Windows.Web.Http.Headers.HttpContentHeaderCollection.ContentLength +P:Windows.Web.Http.Headers.HttpContentHeaderCollection.ContentLocation +P:Windows.Web.Http.Headers.HttpContentHeaderCollection.ContentMD5 +P:Windows.Web.Http.Headers.HttpContentHeaderCollection.ContentRange +P:Windows.Web.Http.Headers.HttpContentHeaderCollection.ContentType +P:Windows.Web.Http.Headers.HttpContentHeaderCollection.Expires +P:Windows.Web.Http.Headers.HttpContentHeaderCollection.LastModified +P:Windows.Web.Http.Headers.HttpRequestHeaderCollection.Authorization +P:Windows.Web.Http.Headers.HttpRequestHeaderCollection.Date +P:Windows.Web.Http.Headers.HttpRequestHeaderCollection.Host +P:Windows.Web.Http.Headers.HttpRequestHeaderCollection.IfModifiedSince +P:Windows.Web.Http.Headers.HttpRequestHeaderCollection.IfUnmodifiedSince +P:Windows.Web.Http.Headers.HttpRequestHeaderCollection.MaxForwards +P:Windows.Web.Http.Headers.HttpRequestHeaderCollection.ProxyAuthorization +P:Windows.Web.Http.Headers.HttpRequestHeaderCollection.Referer +P:Windows.Web.Http.Headers.HttpResponseHeaderCollection.Age +P:Windows.Web.Http.Headers.HttpResponseHeaderCollection.Date +P:Windows.Web.Http.Headers.HttpResponseHeaderCollection.Location +P:Windows.Web.Http.Headers.HttpResponseHeaderCollection.RetryAfter +P:Windows.Web.Http.HttpCookie.Expires +P:Windows.Web.Http.HttpGetInputStreamResult.ExtendedError +P:Windows.Web.Http.HttpGetStringResult.ExtendedError +P:Windows.Web.Http.HttpRequestResult.ExtendedError +P:Windows.Web.Syndication.SyndicationFeed.FirstUri +P:Windows.Web.Syndication.SyndicationFeed.IconUri +P:Windows.Web.Syndication.SyndicationFeed.LastUri +P:Windows.Web.Syndication.SyndicationFeed.NextUri +P:Windows.Web.Syndication.SyndicationFeed.PreviousUri +P:Windows.Web.Syndication.SyndicationItem.EditMediaUri +P:Windows.Web.Syndication.SyndicationItem.EditUri diff --git a/tools/dynwinrt-codegen/scripts/extract-null-results.py b/tools/dynwinrt-codegen/scripts/extract-null-results.py new file mode 100644 index 00000000..837e9546 --- /dev/null +++ b/tools/dynwinrt-codegen/scripts/extract-null-results.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +"""Extract the Windows SDK members whose documented result can be null. + +WinRT metadata carries no nullability, so dynwinrt-codegen types most outputs +as non-null and keeps `| None` for members whose documentation says that the +result can be null. This script derives that list from MicrosoftDocs/winrt-api +at a pinned commit and writes only doc comment IDs (api-ids), never +documentation text, to api-docs/windows-null-results.txt. + +A method (`M:`) or property (`P:`) is listed when +- a sentence of its `## -returns` or `## -property-value` section says that + the result can be null (for an asynchronous method: its completed result), or +- a sentence of its `## -remarks` section says that the member itself + ("this method", "this property", "it", or the member's name) returns or is + null, or requires the caller to check that this member's return value is not + null before using it. +Other sentences that negate null, that describe null arguments, or that +describe an object holding a null value are ignored, and so are Boolean +results, attached properties, constructors and members of generic types. +Reviewed corrections from api-docs/windows-null-results.overrides.txt are +applied last. + +The documentation is read from git objects, without a working tree: + + python extract-null-results.py # fetch the pinned commit + python extract-null-results.py --repo DIR # reuse a local clone + python extract-null-results.py --commit SHA # move the pin +""" + +from __future__ import annotations + +import argparse +import fnmatch +import re +import subprocess +import sys +import tempfile +from collections import Counter +from collections.abc import Iterator +from pathlib import Path + +REPOSITORY = "https://github.com/MicrosoftDocs/winrt-api" +COMMIT = "8448d5eecfbc2ed903f659f350841dcb4888bc8b" +CODEGEN = Path(__file__).resolve().parent.parent +OUTPUT = CODEGEN / "api-docs" / "windows-null-results.txt" +OVERRIDES = CODEGEN / "api-docs" / "windows-null-results.overrides.txt" + +NULL = re.compile(r"\bnull(?:ptr)?\b(?![- ](?:terminat|character|char\b))", re.I) +NEGATION = re.compile( + r"\b(?:never|not|cannot|can't|won't|doesn't|does not|isn't|is not|will not|must not|may not)" + r"\s+(?:be\s+|is\s+|return\s+|returns\s+)?(?:a\s+)?null\b|\bnon-?null\b", + re.I, +) +ARGUMENT = re.compile( + r"\bnull\s+(?:was|is|were|are)\s+passed\b" + r"|\bpass(?:es|ed|ing)?\s+(?:in\s+)?(?:a\s+)?null\b" + r"|\bother than\s+null\b" + r"|\b(?:argument|parameter)\s+(?:is|was|are)\s+null\b" + r"|\bnull\s+(?:for|as)\s+(?:the\s+)?\w+\s+(?:argument|parameter)\b" + r"|\bexception\b[^.]*\bset\s+to\s+null\b|\bset\s+to\s+null\b[^.]*\bexception\b", + re.I, +) +HOLDER = re.compile( + r"\b(?:with|holds?|holding|contains?|containing|supports?|has|have)\s+(?:a|an)\s+" + r"(?:json\s+)?null\s+value\b", + re.I, +) +BOOLEAN = re.compile(r"\A\W*(?:true|false)\b", re.I) +REMARKS_GAP = r"(?:(?!\b(?:when|if|unless|called|until|whether|and|but)\b)[^.;,]){0,40}?" +REMARKS_VERB = ( + r"\b(?:returns?|is|will\s+be|may\s+be|can\s+be|could\s+be|might\s+be|is\s+set\s+to)" + r"\s+(?:a\s+|an\s+|the\s+)?null(?:ptr)?\b" +) +REQUIRED_NULL_CHECK = re.compile( + r"\b(?:must|should|need(?:s)?\s+to)\b" + r"[^.]{0,100}\bcheck\b" + r"[^.]{0,100}\b(?:the|that|return)\s+value\s+is\s+not\s+null\b", + re.I, +) + + +def git(repo: Path, *args: str) -> str: + return subprocess.run( + ["git", "-C", str(repo), *args], check=True, capture_output=True, text=True + ).stdout + + +def ensure_commit(repo: Path, commit: str) -> None: + if not (repo / ".git").exists(): + repo.mkdir(parents=True, exist_ok=True) + git(repo, "init", "--quiet") + present = subprocess.run( + ["git", "-C", str(repo), "cat-file", "-e", f"{commit}^{{commit}}"], + capture_output=True, + ) + if present.returncode != 0: + git(repo, "fetch", "--quiet", "--depth", "1", REPOSITORY, commit) + + +def documents(repo: Path, commit: str) -> Iterator[str]: + listing = git(repo, "ls-tree", "-r", "-z", commit) + blobs = [] + for entry in listing.split("\0"): + if not entry: + continue + info, path = entry.split("\t", 1) + _, kind, sha = info.split() + if kind == "blob" and path.endswith(".md"): + blobs.append(sha) + with subprocess.Popen( + ["git", "-C", str(repo), "cat-file", "--batch"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) as batch: + assert batch.stdin is not None and batch.stdout is not None + for sha in blobs: + batch.stdin.write(sha.encode() + b"\n") + batch.stdin.flush() + size = int(batch.stdout.readline().split()[2]) + data = batch.stdout.read(size) + batch.stdout.read(1) + yield data.decode("utf-8", "replace").replace("\r\n", "\n") + batch.stdin.close() + + +def front_matter(text: str) -> dict[str, str]: + match = re.match(r"\A---[ \t]*\n(.*?)\n---", text, re.S) + fields = {} + for line in match.group(1).splitlines() if match else []: + key, _, value = line.strip().partition(":") + if key.startswith("-"): + fields[key[1:]] = value.strip() + return fields + + +def section(text: str, name: str) -> str: + match = re.search(rf"^## -{re.escape(name)}[ \t]*\n(.*?)(?=^## -|\Z)", text, re.M | re.S) + return plain(match.group(1)) if match else "" + + +def plain(markdown: str) -> str: + text = re.sub(r"", " ", markdown, flags=re.S) + text = re.sub(r"!?\[([^\]]*)\]\([^)]*\)", r"\1", text) + text = re.sub(r"\[!(?:NOTE|IMPORTANT|TIP|WARNING|CAUTION)\]", " ", text) + text = re.sub(r"(?m)^\s*>\s?", " ", text) + text = re.sub(r"[*_`]", "", text) + return " ".join(text.split()) + + +def sentences(text: str) -> list[str]: + return [sentence for sentence in re.split(r"(?<=[.!?])\s+", text) if NULL.search(sentence)] + + +def states_null(sentence: str) -> bool: + return not (NEGATION.search(sentence) or ARGUMENT.search(sentence) or HOLDER.search(sentence)) + + +def result_is_nullable(result: str) -> bool: + if not result or BOOLEAN.match(result): + return False + return any(states_null(sentence) for sentence in sentences(result)) + + +def requires_return_null_check(sentence: str, member: str) -> bool: + """A required check for a member's return value proves it can be null. + + This is intentionally narrower than a generic "not null" mention. It + requires both the return-value subject and an instruction to check the + returned value before use, so statements such as "always returns a value + that is not null" and checks on input parameters remain exclusions. + """ + source = re.compile( + rf"\breturn value from (?:this\s+(?:method|function|call|operation)|{re.escape(member)})\b", + re.I, + ) + return bool(source.search(sentence) and REQUIRED_NULL_CHECK.search(sentence)) + + +def remarks_say_null(remarks: str, member: str) -> bool: + subject = ( + r"(?:\bthis\s+(?:method|property|function|call|operation)" + r"|\bthe\s+(?:method|property|call|operation)" + rf"|\bit|\b{re.escape(member)})\b" + ) + claim = re.compile(subject + REMARKS_GAP + REMARKS_VERB, re.I) + returns = re.compile(rf"\bif\s+(?:this|it|{re.escape(member)})\s+returns\s+null\b", re.I) + return any( + requires_return_null_check(sentence, member) + or ((claim.search(sentence) or returns.search(sentence)) and states_null(sentence)) + for sentence in sentences(remarks) + ) + + +def member_name(api_id: str) -> str: + return api_id[2:].split("(", 1)[0].rsplit(".", 1)[-1] + + +def classify_document(text: str) -> tuple[str, str | None] | None: + """Return `(api_id, source)` for one supported docs page. + + `source` is `returns`, `property-value`, `remarks`, or `None` when the + member is documented but its result is not documented as nullable. + """ + fields = front_matter(text) + api_id = fields.get("api-id", "") + api_type = fields.get("api-type", "") + if not api_id.startswith(("M:", "P:")) or api_type in { + "winrt attachedproperty", + "winrt constructor", + }: + return None + if "#ctor" in api_id or "`" in api_id.split("(", 1)[0]: + return None + + result_section = "returns" if api_id.startswith("M:") else "property-value" + if result_is_nullable(section(text, result_section)): + return api_id, result_section + if remarks_say_null(section(text, "remarks"), member_name(api_id)): + return api_id, "remarks" + return api_id, None + + +def extract(repo: Path, commit: str) -> tuple[set[str], set[str], Counter[str]]: + documented: set[str] = set() + nullable: set[str] = set() + sources: Counter[str] = Counter() + for text in documents(repo, commit): + classification = classify_document(text) + if classification is None: + continue + api_id, source = classification + documented.add(api_id) + if source is not None: + sources[source] += 1 + nullable.add(api_id) + return documented, nullable, sources + + +def apply_overrides(documented: set[str], nullable: set[str], sources: Counter[str]) -> set[str]: + result = set(nullable) + for number, raw in enumerate(OVERRIDES.read_text(encoding="utf-8").splitlines(), 1): + entry = raw.split("#", 1)[0].strip() + if not entry: + continue + sign, pattern = entry[0], entry[1:].strip() + if sign not in "+-" or not pattern: + sys.exit(f"{OVERRIDES.name}:{number}: expected '+api-id' or '-api-id'") + matches = {api_id for api_id in documented if fnmatch.fnmatchcase(api_id, pattern)} + if not matches: + sys.exit(f"{OVERRIDES.name}:{number}: {pattern} matches no documented member") + if sign == "+": + sources["override additions"] += len(matches - result) + result |= matches + else: + sources["override removals"] += len(matches & result) + result -= matches + return result + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__.split("\n\n", 1)[0]) + parser.add_argument("--commit", default=COMMIT, help="winrt-api commit to read") + parser.add_argument("--repo", type=Path, help="local winrt-api clone to reuse") + arguments = parser.parse_args() + + with tempfile.TemporaryDirectory(prefix="winrt-api-") as scratch: + repo = arguments.repo or Path(scratch) + ensure_commit(repo, arguments.commit) + documented, nullable, sources = extract(repo, arguments.commit) + members = sorted(apply_overrides(documented, nullable, sources)) + + header = [ + "# Windows SDK members whose documented result can be null: a method's", + "# return value (for asynchronous methods, the completed result) or a", + "# property's value. dynwinrt-codegen keeps `| None` on these outputs.", + f"# Source: {REPOSITORY} at commit {arguments.commit}", + "# Generated by scripts/extract-null-results.py; do not edit. Reviewed", + "# corrections belong in windows-null-results.overrides.txt.", + ] + OUTPUT.write_text("\n".join(header + members) + "\n", encoding="utf-8", newline="\n") + print(f"{len(members)} members from {len(documented)} documented methods and properties") + for source, count in sources.most_common(): + print(f" {source}: {count}") + + +if __name__ == "__main__": + main() diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/delegates.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/delegates.rs index 9ba6d4f7..c8221c1e 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/delegates.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/delegates.rs @@ -8,13 +8,12 @@ //! substituted, drives both the `Callable[...]` annotation offered to callers //! and the adapter that projects native arguments before the callable runs. -use crate::codegen::winrt::shared::imports::ireference_inner_type; use crate::meta::{ParamDirection, ParamMeta}; use crate::types::TypeMeta; use super::naming::PythonProjectionContext; use super::signature::{py_convert_return, py_runtime_symbol}; -use super::type_helpers::{py_output_type, py_return_type, py_return_type_safe}; +use super::type_helpers::{py_callback_param_type, py_return_type_safe}; /// Generated symbols that describe a delegate's native ABI. pub(crate) struct DelegateAbi { @@ -64,12 +63,11 @@ fn callback_params<'a>( /// Annotation of one argument passed to a Python callback. /// -/// Callback arguments are annotated as non-null, except WinRT `Object` and -/// `IReference`. WinMD metadata does not record nullability, so this is an -/// optimistic policy shared with method outputs: the runtime still passes -/// `None` for a null reference. Every callback-argument annotation goes through -/// this function so a position-aware output-nullability policy can take it -/// over. +/// Callback arguments are annotated as non-null, except WinRT `Object`, +/// `IReference`, and delegate-typed raw values. WinMD metadata does not +/// record nullability, so this is an optimistic policy shared with method +/// outputs: the runtime still passes `None` for a null reference. Every +/// callback-argument annotation goes through the central output policy. pub(crate) fn py_delegate_argument_type( typ: &TypeMeta, context: &PythonProjectionContext, @@ -77,9 +75,6 @@ pub(crate) fn py_delegate_argument_type( if typ.is_async() { return "DynWinRTValue".to_string(); } - if context.is_delegate_type(typ) { - return py_output_type(typ, context); - } let unknown = matches!( typ, TypeMeta::RuntimeClass { .. } @@ -87,10 +82,10 @@ pub(crate) fn py_delegate_argument_type( | TypeMeta::Enum { .. } | TypeMeta::Parameterized { .. } ) && !context.is_known_type(typ); - if unknown || matches!(typ, TypeMeta::Object) || ireference_inner_type(typ).is_some() { + if unknown { py_return_type_safe(Some(typ), context) } else { - py_return_type(Some(typ), context) + py_callback_param_type(typ, context) } } 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 09620b72..48d03901 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/class.rs @@ -85,6 +85,13 @@ pub fn generate_class<'a>( needs_legacy_helper, needs_legacy_int_guard, )); + out.push_str(&collection_item_import( + context, + class + .all_interfaces() + .flat_map(|interface| interface.methods.iter()), + collection_iface.is_some(), + )); if has_public_composition { out.push_str( "from dynwinrt import register_xaml_runtime_class as _dynwinrt_register_xaml_runtime_class\n", 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 31f50044..4fbb39d2 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/mod.rs @@ -32,7 +32,7 @@ use super::naming::{PythonProjectionContext, PythonSupportSymbol, is_py_reserved use super::shared::reorder_getters_before_setters; use super::signature::{ py_collect_argument_iid_consts, py_dynwinrt_type, py_generate_interface_registration, - py_interface_iid_expr, py_runtime_named_symbol, py_runtime_symbol, py_wrap_native_value, + py_interface_iid_expr, py_runtime_named_symbol, py_runtime_symbol, py_wrap_collection_item, }; use super::structs::{ py_struct_field_getter, py_struct_field_read_type, py_struct_field_setter, py_struct_field_type, @@ -82,6 +82,28 @@ from ._runtime import ( ) } +fn collection_item_import<'a>( + context: &PythonProjectionContext, + methods: impl IntoIterator, + collection_interface: bool, +) -> String { + let method_uses_helper = methods.into_iter().any(|method| { + !method.collection_inputs.is_empty() + || method.params.iter().any(|parameter| { + parameter.direction == ParamDirection::In + && super::collections::type_kind(¶meter.typ).is_some() + }) + }); + if collection_interface || method_uses_helper { + format!( + "from ._runtime import {}\n", + context.support_symbol_import(PythonSupportSymbol::CollectionItem) + ) + } else { + String::new() + } +} + const RUNTIME_SUPPORT_BODY: &str = "\ from builtins import property as _property from functools import lru_cache @@ -117,6 +139,14 @@ def _dynwinrt_wrap_values(module, name, values): return [None if value.is_null() else wrap(value) for value in values] +def _dynwinrt_collection_item(value, wrap, allow_none, role): + if value is None: + if allow_none: + return DynWinRTValue.null_value() + raise TypeError(f'{role} cannot be None') + return wrap(value) + + def _dynwinrt_enum(module, name, value): enum_type = _dynwinrt_symbol(module, name) try: @@ -253,9 +283,30 @@ def _dynwinrt_unbox_reference(value): "; fn has_ireference_input<'a>(methods: impl IntoIterator) -> bool { + fn contains(typ: &TypeMeta) -> bool { + if ireference_inner_type(typ).is_some() { + return true; + } + match typ { + TypeMeta::Array(inner) + | TypeMeta::AsyncOperation(inner) + | TypeMeta::AsyncActionWithProgress(inner) => contains(inner), + TypeMeta::AsyncOperationWithProgress(result, progress) => { + contains(result) || contains(progress) + } + TypeMeta::Parameterized { args, .. } => args.iter().any(contains), + // Struct inputs use their generated pack helpers; those modules + // emit IREFERENCE_HELPER through has_ireference_struct_field. + _ => false, + } + } + methods.into_iter().any(|method| { method.params.iter().any(|param| { - param.direction == ParamDirection::In && ireference_inner_type(¶m.typ).is_some() + matches!( + param.direction, + ParamDirection::In | ParamDirection::OutFill + ) && contains(¶m.typ) }) }) } @@ -324,7 +375,47 @@ pub use types::{generate_enum, generate_interface}; #[cfg(test)] mod tests { - use super::generate_runtime_support_module; + use super::*; + use crate::meta::{InterfaceMeta, ParamDirection, ParamMeta}; + + fn reference() -> TypeMeta { + TypeMeta::Parameterized { + namespace: "Windows.Foundation".into(), + name: "IReference`1".into(), + piid: "61c17706-2d65-11e0-9ae8-d48564015472".into(), + args: vec![TypeMeta::I32], + } + } + + fn parameterized(name: &str, piid: &str, args: Vec) -> TypeMeta { + TypeMeta::Parameterized { + namespace: "Windows.Foundation.Collections".into(), + name: name.into(), + piid: piid.into(), + args, + } + } + + fn method(typ: TypeMeta) -> MethodMeta { + MethodMeta { + params: vec![ParamMeta { + name: "value".into(), + typ, + direction: ParamDirection::In, + }], + ..Default::default() + } + } + + fn consumer(methods: Vec) -> InterfaceMeta { + InterfaceMeta { + namespace: "Contoso".into(), + name: "IConsumer".into(), + iid: "11111111-1111-1111-1111-111111111111".into(), + methods, + ..Default::default() + } + } #[test] fn generated_delegates_use_thread_affine_callback_contexts() { @@ -335,6 +426,79 @@ mod tests { assert!(!runtime.contains("copy_context")); } + #[test] + fn ireference_helper_detection_follows_nested_input_wrappers() { + let iterable = parameterized( + "IIterable`1", + super::super::collections::IITERABLE_PIID, + vec![reference()], + ); + let mapping = parameterized( + "IMap`2", + super::super::collections::IMAP_PIID, + vec![TypeMeta::String, reference()], + ); + assert!(has_ireference_input([&method(iterable)])); + assert!(has_ireference_input([&method(mapping)])); + assert!(has_ireference_input([&method(TypeMeta::Array(Box::new( + reference() + )))])); + } + + #[test] + fn ireference_helper_detection_ignores_non_reference_nested_inputs() { + let iterable = parameterized( + "IIterable`1", + super::super::collections::IITERABLE_PIID, + vec![TypeMeta::I32], + ); + let mapping = parameterized( + "IMap`2", + super::super::collections::IMAP_PIID, + vec![TypeMeta::String, TypeMeta::I32], + ); + assert!(!has_ireference_input([&method(iterable)])); + assert!(!has_ireference_input([&method(mapping)])); + } + + #[test] + fn generated_nested_ireference_inputs_emit_the_boxing_helper() { + let reference = reference(); + let iterable = parameterized( + "IIterable`1", + super::super::collections::IITERABLE_PIID, + vec![reference.clone()], + ); + let mapping = parameterized( + "IMap`2", + super::super::collections::IMAP_PIID, + vec![TypeMeta::String, reference], + ); + let interface = consumer(vec![method(iterable), method(mapping)]); + let context = PythonProjectionContext::standalone([interface.type_identity()]).unwrap(); + let generated = generate_interface(&context, &interface); + + assert!(generated.contains("def _dynwinrt_box_reference(value, value_type, wrap):")); + assert!(generated.contains( + "_dynwinrt_vector(value, lambda item: _dynwinrt_collection_item(item, lambda item: _dynwinrt_box_reference(" + )); + assert!(generated.contains( + "_dynwinrt_map(value, lambda item: _dynwinrt_collection_item(item, lambda item: DynWinRTValue.from_hstring(item)" + )); + assert!(generated.contains( + "lambda item: _dynwinrt_collection_item(item, lambda item: _dynwinrt_box_reference(" + )); + + let scalar = consumer(vec![method(parameterized( + "IIterable`1", + super::super::collections::IITERABLE_PIID, + vec![TypeMeta::I32], + ))]); + let generated = generate_interface(&context, &scalar); + assert!(!generated.contains("def _dynwinrt_box_reference(value, value_type, wrap):")); + assert!(!generated.contains("_dynwinrt_box_reference(")); + } + #[test] fn generated_cast_guard_rejects_null_and_non_object_values() { let runtime = generate_runtime_support_module(); 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 60ad336e..70ba4b24 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/types.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/generator/types.rs @@ -11,6 +11,7 @@ use crate::codegen::winrt::python::collections::{ observable_vector_identity, runtime_mixin, }; use crate::codegen::winrt::python::member_plan::{PlannedMember, interface_member_plan}; +use crate::meta::CollectionInputRole; use crate::types::{TypeIdentity, TypeIdentityKind}; /// Generate a Python file for a single enum. @@ -99,6 +100,11 @@ pub fn generate_interface(context: &PythonProjectionContext, iface: &InterfaceMe plan.has_legacy_fallback(), plan.has_legacy_int_guard(), )); + out.push_str(&collection_item_import( + context, + iface.methods.iter(), + interface_kind(iface).is_some(), + )); if implementation.supported { out.push_str(super::super::implementation::IMPORTS); } @@ -356,11 +362,17 @@ pub fn generate_interface(context: &PythonProjectionContext, iface: &InterfaceMe if let Some(ref piid) = iface.generic_piid { if piid == "5917eb53-50b4-4a0d-b309-65862b3f1dbc" && iface.generic_args.len() == 1 { let elem_type = py_dynwinrt_type(&iface.generic_args[0]); - let elem_annotation = crate::codegen::winrt::python::type_helpers::py_param_type_safe( + let elem_annotation = + crate::codegen::winrt::python::type_helpers::py_collection_input_type( + &iface.generic_args[0], + context, + ); + let wrap = py_wrap_collection_item( + "item", &iface.generic_args[0], + CollectionInputRole::Element, context, ); - let wrap = py_wrap_native_value("item", &iface.generic_args[0], context); let vector_identity = observable_vector .as_ref() .expect("observable vector companion"); @@ -381,11 +393,17 @@ pub fn generate_interface(context: &PythonProjectionContext, iface: &InterfaceMe out.push('\n'); } else if piid == "913337e9-11a1-4345-a3a2-4e7f956e222d" && iface.generic_args.len() == 1 { let elem_type = py_dynwinrt_type(&iface.generic_args[0]); - let elem_annotation = crate::codegen::winrt::python::type_helpers::py_param_type_safe( + let elem_annotation = + crate::codegen::winrt::python::type_helpers::py_collection_input_type( + &iface.generic_args[0], + context, + ); + let wrap = py_wrap_collection_item( + "item", &iface.generic_args[0], + CollectionInputRole::Element, context, ); - let wrap = py_wrap_native_value("item", &iface.generic_args[0], context); out.push_str(" @staticmethod\n"); out.push_str(&format!( " def create(items: Iterable[{}]) -> '{}':\n", @@ -399,16 +417,28 @@ pub fn generate_interface(context: &PythonProjectionContext, iface: &InterfaceMe } else if piid == "3c2925fe-8519-45c1-aa79-197b6718c1c1" && iface.generic_args.len() == 2 { let key_type = py_dynwinrt_type(&iface.generic_args[0]); let val_type = py_dynwinrt_type(&iface.generic_args[1]); - let key_annotation = crate::codegen::winrt::python::type_helpers::py_param_type_safe( + let key_annotation = + crate::codegen::winrt::python::type_helpers::py_collection_input_type( + &iface.generic_args[0], + context, + ); + let val_annotation = + crate::codegen::winrt::python::type_helpers::py_collection_input_type( + &iface.generic_args[1], + context, + ); + let wrap_key = py_wrap_collection_item( + "item", &iface.generic_args[0], + CollectionInputRole::Key, context, ); - let val_annotation = crate::codegen::winrt::python::type_helpers::py_param_type_safe( + let wrap_value = py_wrap_collection_item( + "item", &iface.generic_args[1], + CollectionInputRole::Value, context, ); - let wrap_key = py_wrap_native_value("item", &iface.generic_args[0], context); - let wrap_value = py_wrap_native_value("item", &iface.generic_args[1], context); out.push_str(" @staticmethod\n"); out.push_str(&format!( " def create(items: Mapping[{}, {}]) -> '{}':\n", @@ -728,7 +758,7 @@ mod tests { } #[test] - fn collection_create_inputs_remain_non_nullable() { + fn collection_create_reference_inputs_accept_none() { let iface = InterfaceMeta { name: "IVector_Widget".into(), iid: "913337e9-11a1-4345-a3a2-4e7f956e222d".into(), @@ -747,7 +777,127 @@ mod tests { .unwrap(); let code = generate_interface(&context, &iface); - assert!(code.contains("def create(items: Iterable['WidgetLike'])")); - assert!(!code.contains("def create(items: Iterable[WidgetLike | None])")); + assert!(code.contains("def create(items: Iterable[WidgetLike | None])")); + assert!(code.contains( + "_dynwinrt_collection_item(item, lambda item: getattr(item, '_obj', item), True, 'collection element')" + )); + } + + #[test] + fn collection_factories_alias_a_colliding_item_helper() { + let colliding = TypeMeta::RuntimeClass { + namespace: "Contoso".into(), + name: "_dynwinrt_collection_item".into(), + default_interface: None, + }; + let vector = InterfaceMeta { + name: "IVector_Collision".into(), + iid: "913337e9-11a1-4345-a3a2-4e7f956e222d".into(), + generic_piid: Some("913337e9-11a1-4345-a3a2-4e7f956e222d".into()), + generic_args: vec![colliding.clone()], + methods: vec![MethodMeta { + name: "Append".into(), + raw_name: "Append".into(), + vtable_index: 13, + params: vec![crate::meta::ParamMeta { + name: "value".into(), + typ: colliding.clone(), + direction: ParamDirection::In, + }], + collection_inputs: vec![(0, crate::meta::CollectionInputRole::Element)], + ..Default::default() + }], + ..Default::default() + }; + let map = InterfaceMeta { + name: "IMap_Collision_Collision".into(), + iid: "3c2925fe-8519-45c1-aa79-197b6718c1c1".into(), + generic_piid: Some("3c2925fe-8519-45c1-aa79-197b6718c1c1".into()), + generic_args: vec![colliding.clone(), colliding.clone()], + methods: vec![MethodMeta { + name: "Insert".into(), + raw_name: "Insert".into(), + vtable_index: 10, + params: vec![ + crate::meta::ParamMeta { + name: "key".into(), + typ: colliding.clone(), + direction: ParamDirection::In, + }, + crate::meta::ParamMeta { + name: "value".into(), + typ: colliding.clone(), + direction: ParamDirection::In, + }, + ], + return_type: Some(TypeMeta::Bool), + collection_inputs: vec![ + (0, crate::meta::CollectionInputRole::Key), + (1, crate::meta::CollectionInputRole::Value), + ], + ..Default::default() + }], + ..Default::default() + }; + let interfaces = [vector, map]; + let context = PythonProjectionContext::packaged( + interfaces + .iter() + .map(InterfaceMeta::type_identity) + .chain([colliding.type_identity()]), + ) + .unwrap(); + + for interface in &interfaces { + let code = generate_interface(&context, interface); + assert!( + code.contains( + "from ._runtime import _dynwinrt_collection_item as _dynwinrt_collection_item_2" + ), + "{code}" + ); + assert!(code.contains("def create("), "{code}"); + assert!(code.contains("_dynwinrt_collection_item_2(item,"), "{code}"); + assert!( + !code.contains("lambda item: _dynwinrt_collection_item(item,"), + "{code}" + ); + } + } + + #[test] + fn map_factory_key_nullability_follows_the_declared_key_type() { + let reference_key = TypeMeta::Interface { + namespace: "Windows.Foundation".into(), + name: "IStringable".into(), + iid: "96369f54-8eb6-48f0-abce-c1b211e627c3".into(), + }; + let iface = |key| InterfaceMeta { + name: "IMap_Key_Object".into(), + iid: "3c2925fe-8519-45c1-aa79-197b6718c1c1".into(), + generic_piid: Some("3c2925fe-8519-45c1-aa79-197b6718c1c1".into()), + generic_args: vec![key, TypeMeta::Object], + ..Default::default() + }; + let context = PythonProjectionContext::default(); + + let reference = generate_interface(&context, &iface(reference_key)); + let create = reference + .lines() + .find(|line| line.contains("def create(items: Mapping[")) + .expect("map factory"); + assert!( + create.contains("None"), + "reference-key map factory must accept None: {create}" + ); + assert!(reference.contains("True, 'map key'")); + assert!(reference.contains("True, 'map value'")); + + for key in [TypeMeta::String, TypeMeta::Guid] { + let generated = generate_interface(&context, &iface(key)); + assert!(generated.contains("False, 'map key'")); + assert!(!generated.contains("Mapping[str | None")); + assert!(!generated.contains("Mapping[UUID | None")); + } } } diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/method.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/method.rs index 77201c84..0250b173 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/method.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/method.rs @@ -15,14 +15,15 @@ use super::delegates::{ }; use super::member_plan::{Candidate, LegacyPreservation, MethodGroup}; use super::naming::{PythonProjectionContext, PythonSupportSymbol, to_snake_case}; +use super::nullability::AnnotationSurface; use super::signature::{ LegacyPreservationPredicate, py_convert_return, py_has_permissive_guard, py_interface_cast_guard, py_type_guard, py_wrap_arg, py_wrap_async, - py_wrap_async_with_converters, + py_wrap_async_with_converters, py_wrap_collection_input, }; use super::type_helpers::{ method_pydoc, py_factory_return_type, py_method_abi_output_count, py_method_outputs, - py_method_return_type, py_output_type, py_runtime_param_list, + py_method_return_type, py_property_type, py_runtime_method_param_list, }; fn is_delegate_type(typ: &TypeMeta, context: &PythonProjectionContext) -> bool { @@ -55,13 +56,28 @@ pub(crate) fn py_wrap_method_arg( py_wrap_arg(name, typ, context) } -fn py_build_method_args_expr( - in_params: &[&crate::meta::ParamMeta], - context: &PythonProjectionContext, -) -> String { - in_params +fn py_build_method_args_expr(method: &MethodMeta, context: &PythonProjectionContext) -> String { + method + .params .iter() - .map(|param| py_wrap_method_arg(&to_snake_case(¶m.name), ¶m.typ, context)) + .enumerate() + .filter(|(_, param)| { + matches!( + param.direction, + crate::meta::ParamDirection::In | crate::meta::ParamDirection::OutFill + ) + }) + .map(|(index, param)| { + let name = to_snake_case(¶m.name); + match method + .collection_inputs + .iter() + .find_map(|(parameter, role)| (*parameter == index).then_some(*role)) + { + Some(role) => py_wrap_collection_input(&name, ¶m.typ, role, context), + None => py_wrap_method_arg(&name, ¶m.typ, context), + } + }) .collect::>() .join(", ") } @@ -401,9 +417,14 @@ fn generate_factory_method_invoke_named( name_override: Option<&str>, ) -> String { let in_params = get_in_params(method); - let py_params = py_runtime_param_list(&in_params, context); + let py_params = py_runtime_method_param_list(method, context); - let return_py_type = py_factory_return_type(&context.class_name(class), method, context); + let return_py_type = py_factory_return_type( + &context.class_name(class), + method, + AnnotationSurface::Runtime, + context, + ); let mut out = String::new(); let method_name = name_override @@ -424,7 +445,7 @@ fn generate_factory_method_invoke_named( } out.push_str(&method_pydoc(method, &in_params)); - let args_expr = py_build_method_args_expr(&in_params, context); + let args_expr = py_build_method_args_expr(method, context); let iface_symbol = context.reference_name(&iface.type_identity()); let call_expr = method_call_expr( &context.registration_symbol(iface), @@ -479,9 +500,9 @@ fn generate_static_method_invoke_named( name_override: Option<&str>, ) -> String { let in_params = get_in_params(method); - let py_params = py_runtime_param_list(&in_params, context); + let py_params = py_runtime_method_param_list(method, context); - let py_return = py_method_return_type(method, context); + let py_return = py_method_return_type(method, AnnotationSurface::Runtime, context); let mut out = String::new(); let iface_symbol = context.reference_name(&iface.type_identity()); @@ -524,7 +545,7 @@ fn generate_static_method_invoke_named( )); } out.push_str(&method_pydoc(method, &in_params)); - let args_expr = py_build_method_args_expr(&in_params, context); + let args_expr = py_build_method_args_expr(method, context); let call_expr = method_call_expr( &context.registration_symbol(iface), method, @@ -873,7 +894,7 @@ pub(crate) fn generate_method_body( if method.is_property_getter && in_params.is_empty() { let prop_name = to_snake_case(method.name.strip_prefix("get_").unwrap_or(&method.name)); let py_return = return_type - .map(|typ| py_output_type(typ, context)) + .map(|typ| py_property_type(method, typ, AnnotationSurface::Runtime, context)) .unwrap_or_else(|| "None".to_string()); out.push_str(" @_property\n"); out.push_str(&format!(" def {}(self) -> {}:\n", prop_name, py_return)); @@ -914,8 +935,8 @@ pub(crate) fn generate_method_body( iface_var, method.vtable_index, obj_expr, arg )); } else { - let py_params = py_runtime_param_list(&in_params, context); - let py_return = py_method_return_type(method, context); + let py_params = py_runtime_method_param_list(method, context); + let py_return = py_method_return_type(method, AnnotationSurface::Runtime, context); let method_name = name_override .map(|s| s.to_string()) .unwrap_or_else(|| to_snake_case(&method.name)); @@ -931,7 +952,7 @@ pub(crate) fn generate_method_body( )); out.push_str(&method_pydoc(method, &in_params)); - let args_expr = py_build_method_args_expr(&in_params, context); + let args_expr = py_build_method_args_expr(method, context); let call_expr = method_call_expr(iface_var, method, obj_expr, &args_expr, context); emit_method_result(&mut out, &call_expr, method, context); } diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/mod.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/mod.rs index 3f529291..967787d9 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/mod.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/mod.rs @@ -10,6 +10,7 @@ pub(crate) mod member_plan; pub(crate) mod method; pub(crate) mod naming; mod native_types; +pub(crate) mod nullability; mod shared; pub(crate) mod signature; pub(crate) mod structs; @@ -94,8 +95,18 @@ pub(crate) fn collect_referenced_delegate_names( result: &mut std::collections::HashSet, ) { use crate::types::TypeMeta; - if context.is_delegate_type(typ) { - result.insert(context.identity_for_type(typ)); + if context.is_delegate_type(typ) + && result.insert(context.identity_for_type(typ)) + && let Some(invoke) = context.delegate_invoke(typ) + { + // Invoke arguments may themselves be delegates, whose stub modules + // export ABI constants rather than a Python class to import. + for parameter in &invoke.params { + collect(¶meter.typ, context, result); + } + if let Some(return_type) = &invoke.return_type { + collect(return_type, context, result); + } } match typ { TypeMeta::AsyncActionWithProgress(inner) diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/naming.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/naming.rs index c8a70071..54a5f69b 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/naming.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/naming.rs @@ -19,6 +19,7 @@ pub type PythonTypeIdentity = TypeIdentity; pub(crate) enum PythonSupportSymbol { ObjectInput, AsInterface, + CollectionItem, CanCast, LegacyCall, LegacyIntGuard, @@ -29,6 +30,7 @@ impl PythonSupportSymbol { match self { Self::ObjectInput => "_DynWinRTObject", Self::AsInterface => "_dynwinrt_as_interface", + Self::CollectionItem => "_dynwinrt_collection_item", Self::CanCast => "_dynwinrt_can_cast", Self::LegacyCall => "_dynwinrt_legacy_call", Self::LegacyIntGuard => "_dynwinrt_legacy_int_guard", @@ -972,6 +974,7 @@ impl PythonProjectionContext { for helper in [ PythonSupportSymbol::ObjectInput, PythonSupportSymbol::AsInterface, + PythonSupportSymbol::CollectionItem, PythonSupportSymbol::CanCast, PythonSupportSymbol::LegacyCall, PythonSupportSymbol::LegacyIntGuard, @@ -1544,6 +1547,7 @@ mod tests { let others = [ PythonSupportSymbol::ObjectInput, PythonSupportSymbol::AsInterface, + PythonSupportSymbol::CollectionItem, PythonSupportSymbol::CanCast, PythonSupportSymbol::LegacyCall, PythonSupportSymbol::LegacyIntGuard, @@ -1607,6 +1611,11 @@ mod tests { assert_support_helper_yields_to_visible_roles(PythonSupportSymbol::AsInterface); } + #[test] + fn collection_item_helper_yields_to_visible_roles_without_renaming_metadata() { + assert_support_helper_yields_to_visible_roles(PythonSupportSymbol::CollectionItem); + } + #[test] fn overload_helpers_yield_to_visible_roles_without_renaming_metadata() { for helper in [ diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/nullability.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/nullability.rs new file mode 100644 index 00000000..23552453 --- /dev/null +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/nullability.rs @@ -0,0 +1,533 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! The one policy deciding whether a projected output annotation admits +//! `None`. +//! +//! WinRT metadata carries no nullability, so whether a consumer-facing value +//! is annotated `T | None` depends on the value's type, the position it is +//! received at, facts about the member producing it, and the generated file +//! the annotation is rendered into. Renderers in `type_helpers` spell the +//! non-null type expression; only [`output_admits_none`] decides whether +//! `| None` is appended. + +use crate::codegen::winrt::shared::imports::ireference_inner_type; +use crate::meta::{ElementAccess, MethodMeta}; +use crate::types::TypeMeta; + +use super::collections::CollectionKind; +use super::naming::PythonProjectionContext; + +/// The generated artifact an annotation is rendered into. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum AnnotationSurface { + /// Inline annotations of runtime `.py` modules. `typing.get_type_hints()` + /// exposes them, and checkers read them when stubs are not generated. + Runtime, + /// `.pyi` stubs read by type checkers. + Stub, +} + +/// Where the consumer receives a value from the projection. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum OutputPosition { + /// Return value of a method. + Return, + /// Out value in a method's result tuple. + OutParam, + /// Value read from a property getter. + Property, + /// Completed value of an async operation. + AsyncResult, + /// Progress value reported by an async operation. + AsyncProgress, + /// Element, key or value read from a collection or an array. + CollectionElement, + /// Sender or argument the runtime passes to a consumer callback. + CallbackParam, + /// Instance created by an activation factory. Activation reports failure + /// by raising, so the instance is never null unless the factory follows + /// the `Try*` pattern. + Activation, +} + +/// The collection holding an element. Whether a reference-type element admits +/// `None` depends only on this; see [`element_admits_none`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum ElementContainer { + /// `IIterable`, `IIterator`, `IVectorView`, `IMapView` or `IKeyValuePair`. + View, + /// `IVector`, `IMap` or their observable forms. + Mutable, + /// A map key or `IKeyValuePair.Key`. Reference keys can be null; string + /// and value-type keys cannot. + MapKey, + /// An array returned by a member that does not read collection elements. + Array, +} + +impl ElementContainer { + pub(crate) fn of(kind: CollectionKind) -> Self { + match kind { + CollectionKind::MutableSequence | CollectionKind::MutableMapping => Self::Mutable, + CollectionKind::Iterable + | CollectionKind::Iterator + | CollectionKind::Sequence + | CollectionKind::Mapping + | CollectionKind::KeyValuePair => Self::View, + } + } +} + +impl From for ElementContainer { + fn from(access: ElementAccess) -> Self { + match access { + ElementAccess::ReadOnly => Self::View, + ElementAccess::Mutable => Self::Mutable, + ElementAccess::MapKey => Self::MapKey, + } + } +} + +/// The collection element rule: a WinRT collection interface does not carry +/// the provenance needed to prove that a reference-type element is non-null. +/// In particular, a view or iterator obtained from a mutable collection can +/// expose a null slot. All WinRT collection element reads therefore admit +/// `None`. Returned arrays also preserve null reference slots at runtime, so +/// their reference elements follow the same rule while the array itself +/// remains non-null. +pub(crate) fn element_admits_none(container: ElementContainer) -> bool { + match container { + ElementContainer::View | ElementContainer::Mutable | ElementContainer::Array => true, + ElementContainer::MapKey => false, + } +} + +/// A position plus the facts about the member producing the value. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct OutputSite { + pub(crate) position: OutputPosition, + /// The member follows the `Try*` pattern, so a null result is part of + /// its contract ("not found", "could not parse"). + pub(crate) try_method: bool, + /// The Windows SDK documentation says the member's result can be null. + pub(crate) documented_null: bool, + /// The native member contract marks this exact logical output as an + /// optional interface pointer (`IMapView.Split`'s two halves). + pub(crate) contract_nullable: bool, + /// The collection holding the value: set on collection elements, and on + /// the results of members that read elements of the collection declaring + /// them (`get_at`, `lookup`, `current`, ...). + pub(crate) container: Option, +} + +impl OutputSite { + pub(crate) fn of(position: OutputPosition) -> Self { + Self { + position, + try_method: false, + documented_null: false, + contract_nullable: false, + container: None, + } + } + + pub(crate) fn for_method(method: &MethodMeta, position: OutputPosition) -> Self { + Self { + position, + try_method: is_try_method(method), + documented_null: method.documented_null_result, + contract_nullable: false, + container: method.element_access.map(ElementContainer::from), + } + } + + /// A specific logical method output, carrying per-output native contract + /// facts rather than widening every result of the member. + pub(crate) fn for_method_output( + method: &MethodMeta, + position: OutputPosition, + index: usize, + ) -> Self { + Self { + contract_nullable: method.contract_nullable_outputs.contains(&index), + ..Self::for_method(method, position) + } + } + + /// An element read from a collection of the given kind. + pub(crate) fn element_of(container: ElementContainer) -> Self { + Self::of(OutputPosition::CollectionElement).element_in(container) + } + + /// The site of a value nested in this one, such as an async result. + /// Member facts carry over; the policy decides where they apply. + pub(crate) fn nested(self, position: OutputPosition) -> Self { + Self { position, ..self } + } + + /// The site of an element held by `container`, nested in this value. + pub(crate) fn element_in(self, container: ElementContainer) -> Self { + Self { + position: OutputPosition::CollectionElement, + container: Some(container), + ..self + } + } +} + +/// `TryParse`, `TryGetItemAsync`, ...: the CLR name is `Try` followed by an +/// uppercase letter. +pub(crate) fn is_try_method(method: &MethodMeta) -> bool { + method + .raw_name + .strip_prefix("Try") + .and_then(|rest| rest.chars().next()) + .is_some_and(|next| next.is_ascii_uppercase()) +} + +/// Whether the runtime converts a null ABI value of `typ` to `None`: +/// interface pointers (objects, classes, interfaces, delegates and +/// parameterized interfaces, including `IReference`). Value types, +/// strings, arrays and async operations never project as `None`. +pub(crate) fn may_project_none(typ: &TypeMeta) -> bool { + matches!( + typ, + TypeMeta::Object + | TypeMeta::Delegate { .. } + | TypeMeta::RuntimeClass { .. } + | TypeMeta::Interface { .. } + | TypeMeta::Parameterized { .. } + ) +} + +/// Whether the annotation of `typ` received at `site` admits `None` on +/// `surface`. This is the only place that makes that decision for outputs. +pub(crate) fn output_admits_none( + typ: &TypeMeta, + site: OutputSite, + surface: AnnotationSurface, + context: &PythonProjectionContext, +) -> bool { + if ireference_inner_type(typ).is_some() { + return true; + } + if !may_project_none(typ) { + return false; + } + match surface { + // Runtime annotations stay pessimistic: `typing.get_type_hints()` and + // `--no-pyi` consumers see every reference output as optional. + AnnotationSurface::Runtime => site.position != OutputPosition::Activation, + AnnotationSurface::Stub => stub_output_admits_none(typ, site, context), + } +} + +/// Stubs are optimistic, like the JavaScript declarations: WinRT metadata has +/// no nullability, and most APIs raise instead of returning null. +fn stub_output_admits_none( + typ: &TypeMeta, + site: OutputSite, + context: &PythonProjectionContext, +) -> bool { + use OutputPosition::{Activation, AsyncResult, CollectionElement, OutParam, Property, Return}; + + if site.container == Some(ElementContainer::MapKey) { + // output_admits_none only reaches this branch after + // may_project_none(typ), which deliberately covers the Python + // projection's supported COM-pointer shapes (not async wrappers). + return true; + } + // `Object` positions are frequently null, e.g. the arguments of a + // `TypedEventHandler`. + if matches!(typ, TypeMeta::Object) { + return true; + } + // Delegate-typed values are raw handles that are null while unset. + if context.is_delegate_type(typ) { + return true; + } + // `Try*` members report "not found" through a null result, and the + // Windows SDK documentation names the other members that return null. + let member_result = matches!( + site.position, + Return | OutParam | Property | AsyncResult | Activation + ); + if member_result && (site.try_method || site.documented_null || site.contract_nullable) { + return true; + } + // Collection elements, including the results of `get_at`, `lookup` and + // `current`, follow the collection holding them. + matches!(site.position, CollectionElement | Return | Property) + && site.container.is_some_and(element_admits_none) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn method(raw_name: &str) -> MethodMeta { + MethodMeta { + name: raw_name.into(), + raw_name: raw_name.into(), + ..Default::default() + } + } + + #[test] + fn try_methods_require_an_uppercase_continuation() { + assert!(is_try_method(&method("TryParse"))); + assert!(is_try_method(&method("TryGetItemAsync"))); + assert!(!is_try_method(&method("Try"))); + assert!(!is_try_method(&method("Trying"))); + assert!(!is_try_method(&method("GetTryCount"))); + assert!(!is_try_method(&method("get_TryCount"))); + } + + #[test] + fn nested_sites_keep_member_facts() { + let site = OutputSite::for_method(&method("TryGetItemAsync"), OutputPosition::Return); + assert_eq!( + site.nested(OutputPosition::AsyncResult), + OutputSite { + position: OutputPosition::AsyncResult, + try_method: true, + documented_null: false, + contract_nullable: false, + container: None, + } + ); + assert_eq!( + site.element_in(ElementContainer::Array), + OutputSite { + position: OutputPosition::CollectionElement, + try_method: true, + documented_null: false, + contract_nullable: false, + container: Some(ElementContainer::Array), + } + ); + } + + const POSITIONS: [OutputPosition; 8] = [ + OutputPosition::Return, + OutputPosition::OutParam, + OutputPosition::Property, + OutputPosition::AsyncResult, + OutputPosition::AsyncProgress, + OutputPosition::CollectionElement, + OutputPosition::CallbackParam, + OutputPosition::Activation, + ]; + + fn widget() -> TypeMeta { + TypeMeta::RuntimeClass { + namespace: "Contoso".into(), + name: "Widget".into(), + default_interface: None, + } + } + + fn handler() -> TypeMeta { + TypeMeta::Delegate { + namespace: "Contoso".into(), + name: "Handler".into(), + iid: "11111111-1111-1111-1111-111111111111".into(), + } + } + + fn nullable_u32() -> TypeMeta { + TypeMeta::Parameterized { + namespace: "Windows.Foundation".into(), + name: "IReference`1".into(), + piid: "61c17706-2d65-11e0-9ae8-d48564015472".into(), + args: vec![TypeMeta::U32], + } + } + + fn admits(typ: &TypeMeta, site: OutputSite, surface: AnnotationSurface) -> bool { + output_admits_none(typ, site, surface, &PythonProjectionContext::default()) + } + + #[test] + fn runtime_annotations_stay_pessimistic() { + for position in POSITIONS { + let site = OutputSite::of(position); + let expected = position != OutputPosition::Activation; + for typ in [widget(), handler(), TypeMeta::Object] { + assert_eq!( + admits(&typ, site, AnnotationSurface::Runtime), + expected, + "{typ:?} at {position:?}" + ); + } + assert!(admits(&nullable_u32(), site, AnnotationSurface::Runtime)); + assert!(!admits(&TypeMeta::String, site, AnnotationSurface::Runtime)); + } + } + + #[test] + fn stubs_are_non_null_by_default() { + for position in POSITIONS { + let site = OutputSite::of(position); + assert!( + !admits(&widget(), site, AnnotationSurface::Stub), + "{position:?}" + ); + assert!(!admits(&TypeMeta::I32, site, AnnotationSurface::Stub)); + } + } + + #[test] + fn stub_exceptions_keep_none() { + for position in POSITIONS { + let site = OutputSite::of(position); + assert!(admits(&nullable_u32(), site, AnnotationSurface::Stub)); + assert!(admits(&TypeMeta::Object, site, AnnotationSurface::Stub)); + assert!( + admits(&handler(), site, AnnotationSurface::Stub), + "{position:?}" + ); + } + } + + const MEMBER_RESULTS: [OutputPosition; 5] = [ + OutputPosition::Return, + OutputPosition::OutParam, + OutputPosition::Property, + OutputPosition::AsyncResult, + OutputPosition::Activation, + ]; + + #[test] + fn try_members_keep_none_on_their_results_only() { + let try_get = method("TryGetItemAsync"); + for position in POSITIONS { + assert_eq!( + admits( + &widget(), + OutputSite::for_method(&try_get, position), + AnnotationSurface::Stub + ), + MEMBER_RESULTS.contains(&position), + "{position:?}" + ); + } + assert!(!admits( + &TypeMeta::Bool, + OutputSite::for_method(&try_get, OutputPosition::Return), + AnnotationSurface::Stub + )); + } + + #[test] + fn documented_null_members_keep_none_on_their_results_only() { + let get_default = MethodMeta { + documented_null_result: true, + ..method("GetDefault") + }; + for position in POSITIONS { + assert_eq!( + admits( + &widget(), + OutputSite::for_method(&get_default, position), + AnnotationSurface::Stub + ), + MEMBER_RESULTS.contains(&position), + "{position:?}" + ); + } + let site = OutputSite::for_method(&get_default, OutputPosition::Return); + assert!(admits( + &widget(), + site.element_in(ElementContainer::View), + AnnotationSurface::Stub + )); + } + + #[test] + fn reference_collection_elements_are_nullable_regardless_of_provenance() { + let stub = AnnotationSurface::Stub; + for container in [ElementContainer::View, ElementContainer::Mutable] { + let site = OutputSite::element_of(container); + assert!(admits(&widget(), site, stub), "{container:?}"); + assert!(admits(&TypeMeta::Object, site, stub)); + assert!(admits(&nullable_u32(), site, stub)); + assert!(!admits(&TypeMeta::String, site, stub)); + } + let array = OutputSite::element_of(ElementContainer::Array); + assert!(admits(&widget(), array, stub)); + assert!(admits(&TypeMeta::Object, array, stub)); + assert!(admits(&nullable_u32(), array, stub)); + assert!(!admits(&TypeMeta::String, array, stub)); + assert!(!admits(&TypeMeta::Guid, array, stub)); + let key = OutputSite::element_of(ElementContainer::MapKey); + assert!(admits(&widget(), key, stub)); + assert!(admits(&TypeMeta::Object, key, stub)); + assert!(admits(&nullable_u32(), key, stub)); + assert!(!admits(&TypeMeta::String, key, stub)); + assert!(!admits(&TypeMeta::Guid, key, stub)); + // Async projection shapes are not ordinary collection values in the + // current Python codegen, so this scope stays fail closed. + assert!(!admits(&TypeMeta::AsyncAction, key, stub)); + + for access in [ElementAccess::Mutable, ElementAccess::ReadOnly] { + let get_at = MethodMeta { + element_access: Some(access), + ..method("GetAt") + }; + for position in [OutputPosition::Return, OutputPosition::Property] { + assert!( + admits(&widget(), OutputSite::for_method(&get_at, position), stub), + "{access:?} at {position:?}" + ); + } + assert!(!admits( + &widget(), + OutputSite::for_method(&get_at, OutputPosition::AsyncProgress), + stub + )); + } + assert_eq!( + ElementContainer::of(CollectionKind::MutableSequence), + ElementContainer::Mutable + ); + assert_eq!( + ElementContainer::of(CollectionKind::KeyValuePair), + ElementContainer::View + ); + let key = MethodMeta { + element_access: Some(ElementAccess::MapKey), + ..method("get_Key") + }; + assert!(admits( + &widget(), + OutputSite::for_method(&key, OutputPosition::Property), + stub + )); + } + + #[test] + fn native_contract_nullability_is_per_logical_output() { + let split = MethodMeta { + contract_nullable_outputs: vec![0, 1], + ..method("Split") + }; + for (index, expected) in [(0, true), (1, true), (2, false)] { + assert_eq!( + admits( + &widget(), + OutputSite::for_method_output(&split, OutputPosition::OutParam, index), + AnnotationSurface::Stub + ), + expected, + "logical output {index}" + ); + } + assert!(!admits( + &widget(), + OutputSite::for_method(&split, OutputPosition::Return), + AnnotationSurface::Stub + )); + } +} diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/signature.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/signature.rs index d4003050..fd44ca91 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/signature.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/signature.rs @@ -3,7 +3,7 @@ //! Python method signatures, argument wrapping, and return conversion. -use crate::meta::{InterfaceMeta, MethodMeta, ParamDirection}; +use crate::meta::{CollectionInputRole, InterfaceMeta, MethodMeta, ParamDirection}; use crate::types::{TypeIdentity, TypeIdentityKind, TypeMeta}; use super::naming::{PythonProjectionContext, PythonSupportSymbol, PythonSymbol}; @@ -1031,6 +1031,59 @@ pub(crate) fn py_wrap_native_value( } } +/// Wrap one Python collection item through the shared runtime validator. +/// +/// Reference elements and map values accept `None` and encode a real null +/// `DynWinRTValue`. Map keys and value-type positions reject `None` with a +/// stable `TypeError` before type-specific conversion (and before `.cast()`). +pub(crate) fn py_wrap_collection_item( + name: &str, + typ: &TypeMeta, + role: CollectionInputRole, + context: &PythonProjectionContext, +) -> String { + // The native collection plan accepts null for the Python projection's + // supported COM-pointer shapes in every role, including map keys. String, + // Guid, scalar, enum and struct keys still fail closed here. + let allow_none = super::nullability::may_project_none(typ); + let label = match role { + CollectionInputRole::Element => "collection element", + CollectionInputRole::Key => "map key", + CollectionInputRole::Value => "map value", + }; + format!( + "{}({name}, lambda item: {}, {}, '{label}')", + context.support_symbol_reference(PythonSupportSymbol::CollectionItem), + py_wrap_arg("item", typ, context), + if allow_none { "True" } else { "False" } + ) +} + +/// Wrap a parameter that is itself a collection element/value contract. The +/// `ReplaceAll` array is the only array-shaped case; its individual elements +/// use the same validator as append/insert/set operations. +pub(crate) fn py_wrap_collection_input( + name: &str, + typ: &TypeMeta, + role: CollectionInputRole, + context: &PythonProjectionContext, +) -> String { + if let TypeMeta::Array(inner) = typ { + return format!( + "_dynwinrt_array({}, lambda item: {}, {}, {})", + name, + py_wrap_collection_item("item", inner, role, context), + py_dynwinrt_type(inner), + if matches!(inner.as_ref(), TypeMeta::U8) { + "True" + } else { + "False" + } + ); + } + py_wrap_collection_item(name, typ, role, context) +} + fn py_wrap_collection( name: &str, typ: &TypeMeta, @@ -1057,8 +1110,8 @@ fn py_wrap_collection( return Some(format!( "_dynwinrt_map({}, lambda item: {}, lambda item: {}, {}, {})", name, - py_wrap_native_value("item", key, context), - py_wrap_native_value("item", value, context), + py_wrap_collection_item("item", key, CollectionInputRole::Key, context), + py_wrap_collection_item("item", value, CollectionInputRole::Value, context), py_dynwinrt_type(key), py_dynwinrt_type(value) )); @@ -1071,7 +1124,7 @@ fn py_wrap_collection( return Some(format!( "_dynwinrt_vector({}, lambda item: {}, {})", name, - py_wrap_native_value("item", element, context), + py_wrap_collection_item("item", element, CollectionInputRole::Element, context), py_dynwinrt_type(element) )); } @@ -1538,6 +1591,43 @@ mod tests { ); } + #[test] + fn collection_items_validate_none_before_type_specific_conversion() { + let context = PythonProjectionContext::default(); + let geometry = geometry_type(); + let nullable = + py_wrap_collection_item("value", &geometry, CollectionInputRole::Element, &context); + assert!(nullable.contains("_dynwinrt_collection_item(value,")); + assert!(nullable.contains(".cast(IID_ARG_Microsoft_UI_Xaml_Media_Geometry)")); + assert!(nullable.contains("True, 'collection element'")); + + let key = py_wrap_collection_item("key", &geometry, CollectionInputRole::Key, &context); + assert!(key.contains("True, 'map key'")); + let string_key = + py_wrap_collection_item("key", &TypeMeta::String, CollectionInputRole::Key, &context); + assert!(string_key.contains("False, 'map key'")); + let guid_key = + py_wrap_collection_item("key", &TypeMeta::Guid, CollectionInputRole::Key, &context); + assert!(guid_key.contains("False, 'map key'")); + let scalar = py_wrap_collection_item( + "value", + &TypeMeta::I32, + CollectionInputRole::Value, + &context, + ); + assert!(scalar.contains("DynWinRTValue.from_i32(item)")); + assert!(scalar.contains("False, 'map value'")); + + let array = py_wrap_collection_input( + "items", + &TypeMeta::Array(Box::new(geometry)), + CollectionInputRole::Element, + &context, + ); + assert!(array.starts_with("_dynwinrt_array(items, lambda item:")); + assert!(array.contains("True, 'collection element'")); + } + #[test] fn known_interface_inputs_get_a_query_interface_dispatch_guard() { let stream = TypeMeta::Interface { 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 c9f41b74..793afadd 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/stub_helpers.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/stub_helpers.rs @@ -10,10 +10,11 @@ use crate::types::{FieldMeta, TypeMeta}; use super::delegates::{py_delegate_callable_type, py_delegate_param_type}; use super::naming::{PythonProjectionContext, PythonSymbol, STRUCT_SYMBOLS, to_snake_case}; use super::native_types::{FoundationType, foundation_type}; +use super::nullability::{AnnotationSurface, ElementContainer}; use super::structs::{py_struct_field_read_type, py_struct_field_type}; use super::type_helpers::{ - method_pydoc_with_indent, py_factory_return_type, py_method_return_type, py_output_type, - py_param_list, py_param_type_safe, + method_pydoc_with_indent, py_collection_item_type, py_factory_return_type, + py_method_param_list, py_method_return_type, py_param_type_safe, py_property_type, }; use crate::codegen::winrt::shared::imports::ireference_inner_type; @@ -282,7 +283,7 @@ pub(super) fn emit_method_stub_named( if method.is_property_getter && in_params.is_empty() { let prop_name = to_snake_case(method.name.strip_prefix("get_").unwrap_or(&method.name)); let py_return = return_type - .map(|typ| py_output_type(typ, context)) + .map(|typ| py_property_type(method, typ, AnnotationSurface::Stub, context)) .unwrap_or_else(|| "None".to_string()); out.push_str(&format!("{indent}@builtins.property\n")); emit_documented_stub( @@ -323,8 +324,8 @@ pub(super) fn emit_method_stub_named( ); } } else { - let py_params = py_param_list(&in_params, context); - let py_return = py_method_return_type(method, context); + let py_params = py_method_param_list(method, context); + let py_return = py_method_return_type(method, AnnotationSurface::Stub, context); let method_name = name_override .map(str::to_string) .unwrap_or_else(|| to_snake_case(&method.name)); @@ -333,15 +334,21 @@ pub(super) fn emit_method_stub_named( } else { format!("self, {}", py_params) }; - // 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. Empty - // structural protocols can make mypy consider the override compatible. + // `append` takes the projected input annotation, while the + // MutableSequence base reads elements back as `T | None`: WinRT + // vectors may reject null on mutation, yet anyone can store null in + // them. Empty structural protocols can make mypy consider the + // override compatible. let override_ignore = if overrides_mutable_sequence && method_name == "append" && in_params.first().is_some_and(|param| { py_param_type_safe(¶m.typ, context) - != super::type_helpers::py_return_type_safe(Some(¶m.typ), context) + != py_collection_item_type( + ¶m.typ, + ElementContainer::Mutable, + AnnotationSurface::Stub, + context, + ) }) { " # type: ignore[override, unused-ignore]" } else { @@ -376,12 +383,12 @@ pub(super) fn emit_static_method_stub_named( name_override: Option<&str>, ) -> String { let in_params = get_in_params(method); - let py_params = py_param_list(&in_params, context); + let py_params = py_method_param_list(method, context); let py_return = if is_factory { - py_factory_return_type(class_name, method, context) + py_factory_return_type(class_name, method, AnnotationSurface::Stub, context) } else { - py_method_return_type(method, context) + py_method_return_type(method, AnnotationSurface::Stub, context) }; let mut out = String::new(); diff --git a/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs b/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs index f0831427..55984c62 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/stubs.rs @@ -25,7 +25,7 @@ use crate::codegen::winrt::shared::structs::{ }; use super::collections::{ - CollectionKind, abc_name, class_interface, interface_kind, observable_vector_identity, + CollectionKind, class_interface, interface_kind, observable_vector_identity, projected_interface_kind, }; use super::member_plan::{ @@ -34,6 +34,7 @@ use super::member_plan::{ }; use super::naming::{PythonProjectionContext, PythonSupportSymbol, is_py_reserved, to_snake_case}; use super::native_types::foundation_type; +use super::nullability::{AnnotationSurface, ElementContainer}; use super::shared::reorder_getters_before_setters; use super::signature::py_dynwinrt_type; use super::stub_helpers::{ @@ -444,23 +445,14 @@ pub fn generate_interface_stub(context: &PythonProjectionContext, iface: &Interf } } - let collection_base = - collection_kind - .and_then(abc_name) - .and_then(|abc| match iface.generic_args.as_slice() { - [element] => Some(format!( - "{}[{}]", - abc, - super::type_helpers::py_return_type_safe(Some(element), context) - )), - [key, value] => Some(format!( - "{}[{}, {}]", - abc, - super::type_helpers::py_return_type_safe(Some(key), context), - super::type_helpers::py_return_type_safe(Some(value), context) - )), - _ => None, - }); + let collection_base = collection_kind.and_then(|kind| { + super::type_helpers::py_collection_base_type( + kind, + &iface.generic_args, + AnnotationSurface::Stub, + context, + ) + }); let identity_name = format!("_{}Identity", iface.name); out.push_str(&format!("\nclass {identity_name}(Protocol):\n")); out.push_str(&format!(" def {marker}(self) -> None: ...\n")); @@ -514,7 +506,8 @@ pub fn generate_interface_stub(context: &PythonProjectionContext, iface: &Interf // IVector / IMap create() if let Some(ref piid) = iface.generic_piid { if piid == "5917eb53-50b4-4a0d-b309-65862b3f1dbc" && iface.generic_args.len() == 1 { - let element = super::type_helpers::py_param_type_safe(&iface.generic_args[0], context); + let element = + super::type_helpers::py_collection_input_type(&iface.generic_args[0], context); let vector_name = context.projected_name( observable_vector .as_ref() @@ -531,7 +524,8 @@ pub fn generate_interface_stub(context: &PythonProjectionContext, iface: &Interf vector_name )); } else if piid == "913337e9-11a1-4345-a3a2-4e7f956e222d" && iface.generic_args.len() == 1 { - let element = super::type_helpers::py_param_type_safe(&iface.generic_args[0], context); + let element = + super::type_helpers::py_collection_input_type(&iface.generic_args[0], context); out.push('\n'); out.push_str(" @staticmethod\n"); out.push_str(&format!( @@ -539,8 +533,10 @@ pub fn generate_interface_stub(context: &PythonProjectionContext, iface: &Interf element, iface.name )); } else if piid == "3c2925fe-8519-45c1-aa79-197b6718c1c1" && iface.generic_args.len() == 2 { - let key = super::type_helpers::py_param_type_safe(&iface.generic_args[0], context); - let value = super::type_helpers::py_param_type_safe(&iface.generic_args[1], context); + let key = + super::type_helpers::py_collection_input_type(&iface.generic_args[0], context); + let value = + super::type_helpers::py_collection_input_type(&iface.generic_args[1], context); out.push('\n'); out.push_str(" @staticmethod\n"); out.push_str(&format!( @@ -843,20 +839,14 @@ pub fn generate_class_stub<'a>( } let collection_base = collection_iface - .zip(collection_kind.and_then(abc_name)) - .and_then(|(iface, abc)| match iface.generic_args.as_slice() { - [element] => Some(format!( - "{}[{}]", - abc, - super::type_helpers::py_return_type_safe(Some(element), context) - )), - [key, value] => Some(format!( - "{}[{}, {}]", - abc, - super::type_helpers::py_return_type_safe(Some(key), context), - super::type_helpers::py_return_type_safe(Some(value), context) - )), - _ => None, + .zip(collection_kind) + .and_then(|(iface, kind)| { + super::type_helpers::py_collection_base_type( + kind, + &iface.generic_args, + AnnotationSurface::Stub, + context, + ) }); let mut instance_stub_body = emit_class_instance_stubs( class, @@ -1046,22 +1036,14 @@ pub fn generate_class_stub<'a>( continue; } out.push('\n'); - let required_base = interface_kind(req_iface) - .and_then(abc_name) - .and_then(|abc| match req_iface.generic_args.as_slice() { - [element] => Some(format!( - "{}[{}]", - abc, - super::type_helpers::py_return_type_safe(Some(element), context) - )), - [key, value] => Some(format!( - "{}[{}, {}]", - abc, - super::type_helpers::py_return_type_safe(Some(key), context), - super::type_helpers::py_return_type_safe(Some(value), context) - )), - _ => None, - }); + let required_base = interface_kind(req_iface).and_then(|kind| { + super::type_helpers::py_collection_base_type( + kind, + &req_iface.generic_args, + AnnotationSurface::Stub, + context, + ) + }); if let Some(base) = required_base { out.push_str(&format!("\nclass {symbol}({base}):\n")); } else { @@ -1291,10 +1273,27 @@ fn collection_protocol_stubs( return String::new(); }; let indent = " ".repeat(indent_spaces); + // Item positions inherit the element rule of the collection that owns them. + let container = ElementContainer::of(kind); let item_type = iface .generic_args .first() - .map(|typ| super::type_helpers::py_return_type_safe(Some(typ), context)) + .map(|typ| { + if matches!( + kind, + super::collections::CollectionKind::Mapping + | super::collections::CollectionKind::MutableMapping + ) { + super::type_helpers::py_collection_key_type(typ, AnnotationSurface::Stub, context) + } else { + super::type_helpers::py_collection_item_type( + typ, + container, + AnnotationSurface::Stub, + context, + ) + } + }) .unwrap_or_else(|| "object".to_string()); let item_input = iface .generic_args @@ -1337,7 +1336,14 @@ fn collection_protocol_stubs( let value_type = iface .generic_args .get(1) - .map(|typ| super::type_helpers::py_return_type_safe(Some(typ), context)) + .map(|typ| { + super::type_helpers::py_collection_item_type( + typ, + container, + AnnotationSurface::Stub, + context, + ) + }) .unwrap_or_else(|| "object".to_string()); let mut result = format!( "\n{indent}def __len__(self) -> int: ...\n\ @@ -1453,8 +1459,11 @@ fn emit_constructor_stubs(class: &ClassMeta, context: &PythonProjectionContext) } overloads.sort_by(|left, right| super::member_plan::cmp_python_dispatch_params(left, right)); let mut signatures = HashSet::new(); - overloads - .retain(|params| signatures.insert(super::type_helpers::py_param_list(params, context))); + overloads.retain(|params| { + signatures.insert(super::type_helpers::py_constructor_param_list( + params, context, + )) + }); let count = overloads.len(); for params in &overloads { @@ -1543,10 +1552,7 @@ fn typed_signatures<'m>( methods .into_iter() .filter_map(|method| { - let parameters = super::type_helpers::py_param_list( - &crate::codegen::winrt::shared::imports::get_in_params(method), - context, - ); + let parameters = super::type_helpers::py_method_param_list(method, context); signatures .insert((parameters.clone(), return_type(method))) .then(|| (method, !parameter_signatures.insert(parameters))) @@ -1582,7 +1588,9 @@ fn emit_instance_stub_group( let indent = " ".repeat(indent_spaces); let methods = typed_signatures( group.candidates.iter().map(|candidate| candidate.method), - |method| super::type_helpers::py_method_return_type(method, context), + |method| { + super::type_helpers::py_method_return_type(method, AnnotationSurface::Stub, context) + }, context, ); let overloaded = methods.len() > 1; @@ -1621,7 +1629,9 @@ fn emit_instance_compatibility_alias_stubs( for alias in plan.aliases() { let methods = typed_signatures( alias.signatures.iter().copied(), - |method| super::type_helpers::py_method_return_type(method, context), + |method| { + super::type_helpers::py_method_return_type(method, AnnotationSurface::Stub, context) + }, context, ); for (method, duplicate) in &methods { @@ -1703,9 +1713,14 @@ fn static_return_type( context: &PythonProjectionContext, ) -> String { if is_factory_method(class, method) { - super::type_helpers::py_factory_return_type(class_name, method, context) + super::type_helpers::py_factory_return_type( + class_name, + method, + AnnotationSurface::Stub, + context, + ) } else { - super::type_helpers::py_method_return_type(method, context) + super::type_helpers::py_method_return_type(method, AnnotationSurface::Stub, context) } } 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 049afdc6..bac53d56 100644 --- a/tools/dynwinrt-codegen/src/codegen/winrt/python/type_helpers.rs +++ b/tools/dynwinrt-codegen/src/codegen/winrt/python/type_helpers.rs @@ -7,14 +7,18 @@ use crate::codegen::winrt::shared::docs::{DocText, find_param_doc}; use crate::codegen::winrt::shared::imports::{ fill_array_uses_retval_count, ireference_inner_type, method_abi_output_count, }; -use crate::meta::MethodMeta; +use crate::meta::{CollectionInputRole, MethodMeta}; use crate::types::TypeMeta; -use super::collections::{CollectionKind, is_mapping_input, type_kind}; +use super::collections::{CollectionKind, abc_name, is_mapping_input, type_kind}; use super::docs::format_pydoc; use super::naming::to_snake_case; use super::naming::{PythonProjectionContext, PythonSupportSymbol, PythonSymbol}; use super::native_types::{FoundationType, foundation_type}; +use super::nullability::{ + AnnotationSurface, ElementContainer, OutputPosition, OutputSite, may_project_none, + output_admits_none, +}; /// Build the Python docstring for a method body. Uses snake_case param display /// names (matching the generated signature). Returns an empty string when no @@ -58,25 +62,17 @@ pub(super) fn method_pydoc_with_indent( // ====================================================================== pub(crate) fn py_optional_type(typ: String) -> String { - let unquoted = typ - .strip_prefix('\'') - .and_then(|value| value.strip_suffix('\'')) - .unwrap_or(&typ); + let unquoted = unquoted(&typ); if unquoted.split('|').any(|part| part.trim() == "None") { return unquoted.to_string(); } format!("{} | None", unquoted) } -fn is_nullable_reference_type(typ: &TypeMeta) -> bool { - matches!( - typ, - TypeMeta::Object - | TypeMeta::Delegate { .. } - | TypeMeta::RuntimeClass { .. } - | TypeMeta::Interface { .. } - | TypeMeta::Parameterized { .. } - ) +fn unquoted(typ: &str) -> &str { + typ.strip_prefix('\'') + .and_then(|value| value.strip_suffix('\'')) + .unwrap_or(typ) } fn py_param_type(typ: &TypeMeta, context: &PythonProjectionContext) -> String { @@ -152,85 +148,420 @@ pub(super) fn py_collection_input_type( ) -> String { let input = py_param_type_safe(typ, context); // Keep the existing nullable ABC contract; only widen its projected inputs. - if is_nullable_reference_type(typ) { + if may_project_none(typ) { py_optional_type(input) } else { input } } -pub(crate) fn py_return_type_safe( - typ: Option<&TypeMeta>, +/// Input annotation for an element/value position of a mutable collection. +/// `ReplaceAll` is array-shaped, so nullable reference elements belong inside +/// its `Sequence[...]` rather than on the array parameter itself. +pub(super) fn py_collection_contract_input_type( + typ: &TypeMeta, + context: &PythonProjectionContext, +) -> String { + if let TypeMeta::Array(inner) = typ { + let element = py_native_param_element_type(inner, context); + let element = if may_project_none(inner) { + py_optional_type(element) + } else { + element + }; + return if matches!(inner.as_ref(), TypeMeta::U8) { + format!("DynWinRTArray | bytes | bytearray | Sequence[{element}]") + } else { + format!("DynWinRTArray | Sequence[{element}]") + }; + } + py_collection_input_type(typ, context) +} + +// ====================================================================== +// Output annotations +// +// Rendering and nullability are separate layers: the `spell_*` functions +// produce the non-null type expression for a position, and +// `nullability::output_admits_none` alone decides whether `| None` is added. +// ====================================================================== + +/// Spelling rules of the rendering layer. They predate the nullability policy +/// and are preserved byte-for-byte. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Spelling { + /// Method, out and property results: delegates are raw `DynWinRTValue` + /// handles, also inside async results and arrays. + Member, + /// A standalone value, such as the item type of a collection class. + Value, + /// An element of a returned collection or array: nested generics are + /// named by their projected class. + Element, +} + +impl Spelling { + fn at(position: OutputPosition) -> Self { + match position { + OutputPosition::CollectionElement | OutputPosition::CallbackParam => Self::Value, + OutputPosition::Return + | OutputPosition::OutParam + | OutputPosition::Property + | OutputPosition::AsyncResult + | OutputPosition::AsyncProgress + | OutputPosition::Activation => Self::Member, + } + } +} + +/// Renders the annotation of a value the consumer receives at `site`. +/// +/// Nullability never changes how the base type is spelled: a value that may +/// project as `None` renders the same unquoted expression with or without +/// ` | None`. +pub(crate) fn py_output_annotation( + typ: &TypeMeta, + site: OutputSite, + surface: AnnotationSurface, + context: &PythonProjectionContext, +) -> String { + render_output(typ, Spelling::at(site.position), site, surface, context) +} + +/// Callback parameters keep their projected nominal wrapper types so the +/// callable describes the object actually delivered at runtime. Nullability +/// still comes exclusively from the central output policy. +pub(crate) fn py_callback_param_type(typ: &TypeMeta, context: &PythonProjectionContext) -> String { + let site = OutputSite::of(OutputPosition::CallbackParam); + let base = callback_param_base(typ, site, context); + if may_project_none(typ) && output_admits_none(typ, site, AnnotationSurface::Stub, context) { + py_optional_type(base) + } else { + base + } +} + +fn callback_param_base( + typ: &TypeMeta, + site: OutputSite, context: &PythonProjectionContext, ) -> String { - if let Some(inner) = typ.and_then(ireference_inner_type) { - return py_optional_type(py_return_type_safe(Some(inner), context)); + if context.is_delegate_type(typ) { + return "DynWinRTValue".to_string(); } - if let Some(async_type) = typ.and_then(|typ| py_async_return_type(typ, context)) { - return async_type; + if let Some(inner) = ireference_inner_type(typ) { + return callback_param_base(inner, site, context); } - if let Some(annotation) = typ.and_then(|typ| py_collection_return_type(typ, context)) { - return py_optional_type(annotation); + if matches!(typ, TypeMeta::Parameterized { .. }) { + return format!("'{}'", context.reference_name_for_type(typ)); } + spell_value(typ, site, AnnotationSurface::Stub, context) +} +fn render_output( + typ: &TypeMeta, + spelling: Spelling, + site: OutputSite, + surface: AnnotationSurface, + context: &PythonProjectionContext, +) -> String { + let base = match spelling { + Spelling::Member => spell_member(typ, site, surface, context), + Spelling::Value => spell_value(typ, site, surface, context), + Spelling::Element => py_native_element_type(typ, context), + }; + if !may_project_none(typ) { + return base; + } + if output_admits_none(typ, site, surface, context) { + py_optional_type(base) + } else { + unquoted(&base).to_string() + } +} + +fn spell_member( + typ: &TypeMeta, + site: OutputSite, + surface: AnnotationSurface, + context: &PythonProjectionContext, +) -> String { + if context.is_delegate_type(typ) { + return "DynWinRTValue".to_string(); + } + let nested = |typ: &TypeMeta, position: OutputPosition| { + render_output( + typ, + Spelling::Member, + site.nested(position), + surface, + context, + ) + }; match typ { - Some(typ @ TypeMeta::Enum { .. }) if !context.is_known_type(typ) => "int".to_string(), - Some(typ @ (TypeMeta::RuntimeClass { .. } | TypeMeta::Interface { .. })) - if !context.is_known_type(typ) => - { - "DynWinRTValue | None".to_string() - } - Some(TypeMeta::Array(inner)) => py_array_return_type(inner, context), - Some(typ) if is_nullable_reference_type(typ) => { - py_optional_type(py_return_type(Some(typ), context)) + TypeMeta::Array(inner) if context.is_delegate_type(inner) => format!( + "list[{}]", + render_output( + inner, + Spelling::Member, + array_element(site), + surface, + context + ) + ), + TypeMeta::AsyncOperation(result) => { + format!( + "WinRTCoroutine[{}]", + nested(result, OutputPosition::AsyncResult) + ) } - _ => py_return_type(typ, context), + TypeMeta::AsyncOperationWithProgress(result, progress) => format!( + "WinRTCoroutineWithProgress[{}, {}]", + nested(result, OutputPosition::AsyncResult), + nested(progress, OutputPosition::AsyncProgress) + ), + TypeMeta::AsyncActionWithProgress(progress) => format!( + "WinRTCoroutineWithProgress[None, {}]", + nested(progress, OutputPosition::AsyncProgress) + ), + _ => spell_value(typ, site, surface, context), } } -fn py_async_return_type_with_result( +fn spell_value( typ: &TypeMeta, - result_override: Option, + site: OutputSite, + surface: AnnotationSurface, context: &PythonProjectionContext, -) -> Option { +) -> String { + if let Some(inner) = ireference_inner_type(typ) { + return render_output(inner, Spelling::Value, site, surface, context); + } + if let Some(collection) = spell_collection(typ, site, surface, context) { + return collection; + } + let nested = |typ: &TypeMeta, spelling: Spelling, position: OutputPosition| { + render_output(typ, spelling, site.nested(position), surface, context) + }; match typ { - TypeMeta::AsyncAction => Some("WinRTCoroutine[None]".to_string()), - TypeMeta::AsyncOperation(result) => Some(format!( + TypeMeta::AsyncAction => "WinRTCoroutine[None]".to_string(), + TypeMeta::AsyncOperation(result) => format!( "WinRTCoroutine[{}]", - result_override.unwrap_or_else(|| py_return_type_safe(Some(result), context)) - )), - TypeMeta::AsyncActionWithProgress(progress) => Some(format!( + nested(result, Spelling::Value, OutputPosition::AsyncResult) + ), + TypeMeta::AsyncActionWithProgress(progress) => format!( "WinRTCoroutineWithProgress[None, {}]", - py_return_type_safe(Some(progress), context) - )), - TypeMeta::AsyncOperationWithProgress(result, progress) => Some(format!( + nested(progress, Spelling::Value, OutputPosition::AsyncProgress) + ), + TypeMeta::AsyncOperationWithProgress(result, progress) => format!( "WinRTCoroutineWithProgress[{}, {}]", - result_override.unwrap_or_else(|| py_return_type_safe(Some(result), context)), - py_return_type_safe(Some(progress), context) - )), - _ => None, + nested(result, Spelling::Value, OutputPosition::AsyncResult), + nested(progress, Spelling::Value, OutputPosition::AsyncProgress) + ), + TypeMeta::Enum { .. } if !context.is_known_type(typ) => "int".to_string(), + TypeMeta::RuntimeClass { .. } | TypeMeta::Interface { .. } + if !context.is_known_type(typ) => + { + "DynWinRTValue".to_string() + } + TypeMeta::Array(inner) if matches!(inner.as_ref(), TypeMeta::U8) => "bytes".to_string(), + TypeMeta::Array(inner) => format!( + "list[{}]", + render_output( + inner, + Spelling::Element, + array_element(site), + surface, + context + ) + ), + TypeMeta::String | TypeMeta::Char16 => "str".to_string(), + TypeMeta::Guid => "UUID".to_string(), + TypeMeta::Bool => "bool".to_string(), + TypeMeta::I8 + | TypeMeta::U8 + | TypeMeta::I16 + | TypeMeta::U16 + | TypeMeta::I32 + | TypeMeta::U32 + | TypeMeta::I64 + | TypeMeta::U64 => "int".to_string(), + TypeMeta::F32 | TypeMeta::F64 => "float".to_string(), + TypeMeta::RuntimeClass { .. } + | TypeMeta::Enum { .. } + | TypeMeta::Interface { .. } + | TypeMeta::Parameterized { .. } => { + format!("'{}'", context.reference_name_for_type(typ)) + } + TypeMeta::Object | TypeMeta::Delegate { .. } => "'DynWinRTValue'".to_string(), + TypeMeta::Struct { name, .. } if name == "HResult" => "int".to_string(), + typ if foundation_type(typ) == Some(FoundationType::DateTime) => "datetime".to_string(), + typ if foundation_type(typ) == Some(FoundationType::TimeSpan) => "timedelta".to_string(), + TypeMeta::Struct { .. } => format!("'{}'", context.reference_name_for_type(typ)), } } -pub(super) fn py_async_return_type( +fn spell_collection( + typ: &TypeMeta, + site: OutputSite, + surface: AnnotationSurface, + context: &PythonProjectionContext, +) -> Option { + let TypeMeta::Parameterized { args, .. } = typ else { + return None; + }; + let kind = type_kind(typ)?; + let abc = abc_name(kind)?; + let element = site.element_in(ElementContainer::of(kind)); + let elements = args + .iter() + .enumerate() + .map(|(index, arg)| { + let argument_site = if index == 0 + && matches!( + kind, + CollectionKind::Mapping | CollectionKind::MutableMapping + ) { + site.element_in(ElementContainer::MapKey) + } else { + element + }; + render_output(arg, Spelling::Element, argument_site, surface, context) + }) + .collect::>(); + Some(format!("{abc}[{}]", elements.join(", "))) +} + +/// The elements of an array filled by a member that reads collection +/// elements (`get_many`) follow that collection; other arrays preserve their +/// own nullable reference slots. +fn array_element(site: OutputSite) -> OutputSite { + site.element_in(site.container.unwrap_or(ElementContainer::Array)) +} + +/// Pessimistic rendering for callers outside the output policy (callback +/// parameters and the `IReference` input arm): every value that may +/// project as `None` admits it, as on the runtime surface. +pub(crate) fn py_return_type_safe( + typ: Option<&TypeMeta>, + context: &PythonProjectionContext, +) -> String { + typ.map(|typ| { + render_output( + typ, + Spelling::Value, + OutputSite::of(OutputPosition::CallbackParam), + AnnotationSurface::Runtime, + context, + ) + }) + .unwrap_or_else(|| "None".to_string()) +} + +/// Annotation of the value read by a property getter. +pub(super) fn py_property_type( + getter: &MethodMeta, typ: &TypeMeta, + surface: AnnotationSurface, + context: &PythonProjectionContext, +) -> String { + py_output_annotation( + typ, + OutputSite::for_method(getter, OutputPosition::Property), + surface, + context, + ) +} + +/// Item, key or value type of a projected collection held by `container`. +pub(super) fn py_collection_item_type( + typ: &TypeMeta, + container: ElementContainer, + surface: AnnotationSurface, + context: &PythonProjectionContext, +) -> String { + py_output_annotation(typ, OutputSite::element_of(container), surface, context) +} + +/// Key type of a projected map. WinRT maps reject null keys even when their +/// key ABI type is a reference. +pub(super) fn py_collection_key_type( + typ: &TypeMeta, + surface: AnnotationSurface, + context: &PythonProjectionContext, +) -> String { + py_output_annotation( + typ, + OutputSite::element_of(ElementContainer::MapKey), + surface, + context, + ) +} + +/// `Sequence[T]` / `Mapping[K, V]` base of a projected collection of `kind`. +pub(super) fn py_collection_base_type( + kind: CollectionKind, + args: &[TypeMeta], + surface: AnnotationSurface, context: &PythonProjectionContext, ) -> Option { - py_async_return_type_with_result(typ, None, context) + let abc = abc_name(kind)?; + let item = |typ| py_collection_item_type(typ, ElementContainer::of(kind), surface, context); + match args { + [element] => Some(format!("{abc}[{}]", item(element))), + [key, value] => Some(format!( + "{abc}[{}, {}]", + py_collection_key_type(key, surface, context), + item(value) + )), + _ => None, + } } +/// Result annotation of an activation factory creating `class_name`. pub(super) fn py_factory_return_type( class_name: &str, method: &MethodMeta, + surface: AnnotationSurface, context: &PythonProjectionContext, ) -> String { - method - .return_type - .as_ref() - .and_then(|typ| { - py_async_return_type_with_result(typ, Some(format!("'{}'", class_name)), context) - }) - .unwrap_or_else(|| format!("'{}'", class_name)) + let instance = |typ: &TypeMeta| { + let instance = format!("'{class_name}'"); + let site = OutputSite::for_method(method, OutputPosition::Activation); + if output_admits_none(typ, site, surface, context) { + py_optional_type(instance) + } else { + instance + } + }; + let progress = |typ: &TypeMeta| { + render_output( + typ, + Spelling::Value, + OutputSite::for_method(method, OutputPosition::AsyncProgress), + surface, + context, + ) + }; + match method.return_type.as_ref() { + None => format!("'{class_name}'"), + Some(TypeMeta::AsyncAction) => "WinRTCoroutine[None]".to_string(), + Some(TypeMeta::AsyncOperation(result)) => { + format!("WinRTCoroutine[{}]", instance(result)) + } + Some(TypeMeta::AsyncActionWithProgress(progress_type)) => { + format!( + "WinRTCoroutineWithProgress[None, {}]", + progress(progress_type) + ) + } + Some(TypeMeta::AsyncOperationWithProgress(result, progress_type)) => format!( + "WinRTCoroutineWithProgress[{}, {}]", + instance(result), + progress(progress_type) + ), + Some(typ) => instance(typ), + } } pub(super) fn methods_have_async_output<'a>( @@ -249,20 +580,26 @@ pub(super) fn py_method_abi_output_count(method: &MethodMeta) -> usize { } pub(super) fn py_method_outputs(method: &MethodMeta) -> Vec<(usize, &TypeMeta)> { - let mut result_index = 0; + py_method_output_positions(method) + .into_iter() + .enumerate() + .map(|(result_index, (typ, _))| (result_index, typ)) + .collect() +} + +/// Logical outputs in result order: out values first, then the return value. +fn py_method_output_positions(method: &MethodMeta) -> Vec<(&TypeMeta, OutputPosition)> { let mut outputs = Vec::new(); for param in &method.params { match param.direction { crate::meta::ParamDirection::Out => { - outputs.push((result_index, ¶m.typ)); - result_index += 1; + outputs.push((¶m.typ, OutputPosition::OutParam)); } crate::meta::ParamDirection::OutFill => { // The runtime allocates a distinct filled result buffer; the // caller-provided array supplies capacity and is not mutated. - outputs.push((result_index, ¶m.typ)); - result_index += 1; + outputs.push((¶m.typ, OutputPosition::OutParam)); } crate::meta::ParamDirection::In => {} } @@ -273,108 +610,33 @@ pub(super) fn py_method_outputs(method: &MethodMeta) -> Vec<(usize, &TypeMeta)> .as_ref() .filter(|_| !fill_array_uses_retval_count(method)) { - outputs.push((result_index, return_type)); + outputs.push((return_type, OutputPosition::Return)); } outputs } -fn is_delegate_output(typ: &TypeMeta, context: &PythonProjectionContext) -> bool { - context.is_delegate_type(typ) -} - -pub(super) fn py_output_type(typ: &TypeMeta, context: &PythonProjectionContext) -> String { - match typ { - _ if is_delegate_output(typ, context) => "DynWinRTValue | None".to_string(), - TypeMeta::Array(inner) if is_delegate_output(inner, context) => { - "list[DynWinRTValue | None]".to_string() - } - TypeMeta::AsyncOperation(inner) => { - format!("WinRTCoroutine[{}]", py_output_type(inner, context)) - } - TypeMeta::AsyncOperationWithProgress(result, progress) => format!( - "WinRTCoroutineWithProgress[{}, {}]", - py_output_type(result, context), - py_output_type(progress, context) - ), - TypeMeta::AsyncActionWithProgress(progress) => format!( - "WinRTCoroutineWithProgress[None, {}]", - py_output_type(progress, context) - ), - _ => py_return_type_safe(Some(typ), context), - } -} - pub(super) fn py_method_return_type( method: &MethodMeta, + surface: AnnotationSurface, context: &PythonProjectionContext, ) -> String { - let outputs = py_method_outputs(method); + let outputs = py_method_output_positions(method) + .into_iter() + .enumerate() + .map(|(index, (typ, position))| { + py_output_annotation( + typ, + OutputSite::for_method_output(method, position, index), + surface, + context, + ) + }) + .collect::>(); match outputs.as_slice() { [] => "None".to_string(), - [(_, typ)] => py_output_type(typ, context), - _ => format!( - "tuple[{}]", - outputs - .iter() - .map(|(_, typ)| py_output_type(typ, context)) - .collect::>() - .join(", ") - ), - } -} - -pub(super) fn py_return_type(typ: Option<&TypeMeta>, context: &PythonProjectionContext) -> String { - match typ { - Some(TypeMeta::String) => "str".to_string(), - Some(TypeMeta::Guid) => "UUID".to_string(), - Some(TypeMeta::Bool) => "bool".to_string(), - Some( - TypeMeta::I8 - | TypeMeta::U8 - | TypeMeta::I16 - | TypeMeta::U16 - | TypeMeta::I32 - | TypeMeta::U32 - | TypeMeta::I64 - | TypeMeta::U64, - ) => "int".to_string(), - Some(TypeMeta::Char16) => "str".to_string(), - Some(TypeMeta::F32 | TypeMeta::F64) => "float".to_string(), - Some(typ @ TypeMeta::RuntimeClass { .. }) - | Some(typ @ TypeMeta::Enum { .. }) - | Some(typ @ TypeMeta::Interface { .. }) => { - format!("'{}'", context.reference_name_for_type(typ)) - } - Some(typ @ TypeMeta::Parameterized { .. }) => { - format!("'{}'", context.reference_name_for_type(typ)) - } - Some(TypeMeta::AsyncOperation(inner)) => { - format!("WinRTCoroutine[{}]", py_return_type(Some(inner), context)) - } - Some(TypeMeta::AsyncOperationWithProgress(result, progress)) => format!( - "WinRTCoroutineWithProgress[{}, {}]", - py_return_type(Some(result), context), - py_return_type(Some(progress), context) - ), - Some(TypeMeta::AsyncAction) => "WinRTCoroutine[None]".to_string(), - Some(TypeMeta::AsyncActionWithProgress(progress)) => format!( - "WinRTCoroutineWithProgress[None, {}]", - py_return_type(Some(progress), context) - ), - Some(TypeMeta::Array(inner)) => py_array_return_type(inner, context), - Some(TypeMeta::Object) | Some(TypeMeta::Delegate { .. }) => "'DynWinRTValue'".to_string(), - Some(TypeMeta::Struct { name, .. }) if name == "HResult" => "int".to_string(), - Some(typ) if foundation_type(typ) == Some(FoundationType::DateTime) => { - "datetime".to_string() - } - Some(typ) if foundation_type(typ) == Some(FoundationType::TimeSpan) => { - "timedelta".to_string() - } - Some(typ @ TypeMeta::Struct { .. }) => { - format!("'{}'", context.reference_name_for_type(typ)) - } - None => "None".to_string(), + [output] => output.clone(), + _ => format!("tuple[{}]", outputs.join(", ")), } } @@ -441,20 +703,6 @@ fn py_native_param_element_type(inner: &TypeMeta, context: &PythonProjectionCont } } -fn py_array_return_type(inner: &TypeMeta, context: &PythonProjectionContext) -> String { - if matches!(inner, TypeMeta::U8) { - "bytes".to_string() - } else { - let element = py_native_element_type(inner, context); - let element = if is_nullable_reference_type(inner) { - py_optional_type(element) - } else { - element - }; - format!("list[{element}]") - } -} - fn py_collection_param_type(typ: &TypeMeta, context: &PythonProjectionContext) -> Option { let TypeMeta::Parameterized { args, .. } = typ else { return None; @@ -476,8 +724,8 @@ fn py_collection_param_type(typ: &TypeMeta, context: &PythonProjectionContext) - }; return Some(format!( "Mapping[{}, {}]", - py_native_param_element_type(key, context), - py_native_param_element_type(value, context) + py_collection_input_type(key, context), + py_collection_input_type(value, context) )); } let element = args.first()?; @@ -494,62 +742,80 @@ fn py_collection_param_type(typ: &TypeMeta, context: &PythonProjectionContext) - } else { "Sequence" }, - py_native_param_element_type(element, context) + py_collection_input_type(element, context) )), _ => None, } } -fn py_collection_return_type(typ: &TypeMeta, context: &PythonProjectionContext) -> Option { - let TypeMeta::Parameterized { args, .. } = typ else { - return None; - }; - let kind = type_kind(typ)?; - let abc = super::collections::abc_name(kind)?; - let types = args - .iter() - .map(|arg| { - let element = py_native_element_type(arg, context); - if is_nullable_reference_type(arg) { - py_optional_type(element) - } else { - element - } - }) - .collect::>(); - Some(format!("{abc}[{}]", types.join(", "))) -} - -pub(super) fn py_param_list( - in_params: &[&crate::meta::ParamMeta], +/// Method parameters with collection input roles applied. Supported reference +/// elements, values, and keys gain `None`; general WinRT inputs keep their +/// existing annotations. +pub(super) fn py_method_param_list( + method: &MethodMeta, context: &PythonProjectionContext, ) -> String { - in_params + method + .params .iter() - .map(|p| { - let param_type = match &p.typ { - typ if context.is_delegate_type(typ) => { - super::delegates::py_delegate_param_type(typ, context) + .enumerate() + .filter(|(_, param)| { + matches!( + param.direction, + crate::meta::ParamDirection::In | crate::meta::ParamDirection::OutFill + ) + }) + .map(|(index, param)| { + let role = method + .collection_inputs + .iter() + .find_map(|(parameter, role)| (*parameter == index).then_some(*role)); + let param_type = match role { + Some(CollectionInputRole::Element | CollectionInputRole::Value) => { + py_collection_contract_input_type(¶m.typ, context) + } + Some(CollectionInputRole::Key) => py_collection_input_type(¶m.typ, context), + None if context.is_delegate_type(¶m.typ) => { + super::delegates::py_delegate_param_type(¶m.typ, context) } - _ => py_param_type_safe(&p.typ, context), + None => py_param_type_safe(¶m.typ, context), }; - format!("{}: {}", to_snake_case(&p.name), param_type) + format!("{}: {}", to_snake_case(¶m.name), param_type) }) .collect::>() .join(", ") } -pub(super) fn py_runtime_param_list( - in_params: &[&crate::meta::ParamMeta], +/// Runtime method parameters use broad, eagerly resolvable delegate +/// annotations while retaining collection element/key/value input contracts. +pub(super) fn py_runtime_method_param_list( + method: &MethodMeta, context: &PythonProjectionContext, ) -> String { - in_params + method + .params .iter() - .map(|param| { - let param_type = if context.is_delegate_type(¶m.typ) { - super::delegates::py_runtime_delegate_param_type().to_string() - } else { - py_param_type_safe(¶m.typ, context) + .enumerate() + .filter(|(_, param)| { + matches!( + param.direction, + crate::meta::ParamDirection::In | crate::meta::ParamDirection::OutFill + ) + }) + .map(|(index, param)| { + let role = method + .collection_inputs + .iter() + .find_map(|(parameter, role)| (*parameter == index).then_some(*role)); + let param_type = match role { + Some(CollectionInputRole::Element | CollectionInputRole::Value) => { + py_collection_contract_input_type(¶m.typ, context) + } + Some(CollectionInputRole::Key) => py_collection_input_type(¶m.typ, context), + None if context.is_delegate_type(¶m.typ) => { + super::delegates::py_runtime_delegate_param_type().to_string() + } + None => py_param_type_safe(¶m.typ, context), }; format!("{}: {}", to_snake_case(¶m.name), param_type) }) @@ -580,6 +846,19 @@ mod tests { use super::*; use crate::meta::{ParamDirection, ParamMeta}; + fn returned( + typ: &TypeMeta, + surface: AnnotationSurface, + context: &PythonProjectionContext, + ) -> String { + py_output_annotation( + typ, + OutputSite::of(OutputPosition::Return), + surface, + context, + ) + } + #[test] fn multi_out_returns_typed_tuple_in_abi_order() { let method = MethodMeta { @@ -606,7 +885,11 @@ mod tests { assert_eq!(outputs[0], (0, &TypeMeta::U32)); assert_eq!(outputs[1], (1, &TypeMeta::Bool)); assert_eq!( - py_method_return_type(&method, &PythonProjectionContext::default()), + py_method_return_type( + &method, + AnnotationSurface::Stub, + &PythonProjectionContext::default() + ), "tuple[int, bool]" ); } @@ -639,7 +922,11 @@ mod tests { (0, &TypeMeta::Array(Box::new(TypeMeta::String))) ); assert_eq!( - py_method_return_type(&method, &PythonProjectionContext::default()), + py_method_return_type( + &method, + AnnotationSurface::Stub, + &PythonProjectionContext::default() + ), "list[str]" ); assert_eq!( @@ -658,10 +945,88 @@ mod tests { #[test] fn object_arrays_return_typed_runtime_values() { + let array = TypeMeta::Array(Box::new(TypeMeta::Object)); + for surface in [AnnotationSurface::Runtime, AnnotationSurface::Stub] { + assert_eq!( + returned(&array, surface, &PythonProjectionContext::default()), + "list[DynWinRTValue | None]" + ); + } + } + + #[test] + fn ordinary_reference_array_elements_are_nullable_but_the_array_is_not() { + let runtime_class = TypeMeta::RuntimeClass { + namespace: "Contoso".into(), + name: "Widget".into(), + default_interface: None, + }; + let interface = TypeMeta::Interface { + namespace: "Contoso".into(), + name: "IWidget".into(), + iid: "11111111-1111-1111-1111-111111111111".into(), + }; + let delegate = TypeMeta::Delegate { + namespace: "Contoso".into(), + name: "WidgetHandler".into(), + iid: "22222222-2222-2222-2222-222222222222".into(), + }; + let reference = TypeMeta::Parameterized { + namespace: "Windows.Foundation".into(), + name: "IReference`1".into(), + piid: "61c17706-2d65-11e0-9ae8-d48564015472".into(), + args: vec![TypeMeta::U32], + }; + let view = TypeMeta::Parameterized { + namespace: "Windows.Foundation.Collections".into(), + name: "IVectorView`1".into(), + piid: crate::codegen::winrt::python::collections::IVECTOR_VIEW_PIID.into(), + args: vec![runtime_class.clone()], + }; + let context = PythonProjectionContext::standalone([ + runtime_class.type_identity(), + interface.type_identity(), + delegate.type_identity(), + reference.type_identity(), + view.type_identity(), + ]) + .unwrap(); + let stub = AnnotationSurface::Stub; + + for (inner, expected) in [ + (runtime_class.clone(), "list[Widget | None]"), + (interface, "list[IWidget | None]"), + (TypeMeta::Object, "list[DynWinRTValue | None]"), + (delegate, "list[DynWinRTValue | None]"), + (reference, "list[IReference_UInt32 | None]"), + (view, "list[IVectorView_Widget | None]"), + ] { + let annotation = returned(&TypeMeta::Array(Box::new(inner)), stub, &context); + assert_eq!(annotation, expected); + assert!( + !annotation.ends_with("] | None"), + "the array value itself must stay non-null: {annotation}" + ); + } assert_eq!( - py_array_return_type(&TypeMeta::Object, &PythonProjectionContext::default()), - "list[DynWinRTValue | None]" + returned( + &TypeMeta::AsyncOperation(Box::new(TypeMeta::Array(Box::new(runtime_class)))), + stub, + &context + ), + "WinRTCoroutine[list[Widget | None]]" ); + for (inner, expected) in [ + (TypeMeta::String, "list[str]"), + (TypeMeta::Guid, "list[UUID]"), + (TypeMeta::I32, "list[int]"), + (TypeMeta::U8, "bytes"), + ] { + assert_eq!( + returned(&TypeMeta::Array(Box::new(inner)), stub, &context), + expected + ); + } } #[test] @@ -680,13 +1045,13 @@ mod tests { "IIterable`1", "faa585ea-6214-4217-afda-7f46de5869b3", vec![TypeMeta::Object], - "Iterable['DynWinRTValue | _DynWinRTObject']", + "Iterable[DynWinRTValue | _DynWinRTObject | None]", ), ( "IMap`2", "3c2925fe-8519-45c1-aa79-197b6718c1c1", vec![TypeMeta::String, TypeMeta::Object], - "Mapping[str, 'DynWinRTValue | _DynWinRTObject']", + "Mapping[str, DynWinRTValue | _DynWinRTObject | None]", ), ] { let typ = TypeMeta::Parameterized { @@ -698,7 +1063,7 @@ mod tests { assert_eq!(py_param_type_safe(&typ, &context), expected); } assert_eq!( - py_return_type_safe(Some(&TypeMeta::Object), &context), + returned(&TypeMeta::Object, AnnotationSurface::Stub, &context), "DynWinRTValue | None" ); assert_eq!( @@ -725,24 +1090,28 @@ mod tests { }; assert_eq!( py_param_type_safe(&mapping, &context), - "Mapping[str, 'DynWinRTValue | _DynWinRTObject_2']" + "Mapping[str, DynWinRTValue | _DynWinRTObject_2 | None]" ); assert_eq!( py_collection_input_type(&TypeMeta::Object, &context), "DynWinRTValue | _DynWinRTObject_2 | None" ); assert_eq!( - py_return_type_safe(Some(&TypeMeta::Object), &context), + returned(&TypeMeta::Object, AnnotationSurface::Stub, &context), "DynWinRTValue | None" ); assert_eq!( - py_array_return_type(&TypeMeta::Object, &context), + returned( + &TypeMeta::Array(Box::new(TypeMeta::Object)), + AnnotationSurface::Stub, + &context + ), "list[DynWinRTValue | None]" ); } #[test] - fn reference_returns_are_annotated_as_nullable() { + fn runtime_reference_outputs_stay_nullable() { let runtime_class = TypeMeta::RuntimeClass { namespace: "Contoso".into(), name: "Widget".into(), @@ -758,23 +1127,233 @@ mod tests { interface.type_identity(), ]) .unwrap(); + let runtime = AnnotationSurface::Runtime; + assert_eq!(returned(&runtime_class, runtime, &context), "Widget | None"); + assert_eq!(returned(&interface, runtime, &context), "IWidget | None"); + assert_eq!( + returned(&TypeMeta::Object, runtime, &context), + "DynWinRTValue | None" + ); + assert_eq!( + returned( + &TypeMeta::Array(Box::new(runtime_class.clone())), + runtime, + &context + ), + "list[Widget | None]" + ); assert_eq!( py_return_type_safe(Some(&runtime_class), &context), "Widget | None" ); + } + + #[test] + fn stub_outputs_are_non_null_except_policy_exceptions() { + let widget = TypeMeta::RuntimeClass { + namespace: "Contoso".into(), + name: "Widget".into(), + default_interface: None, + }; + let interface = TypeMeta::Interface { + namespace: "Contoso".into(), + name: "IWidget".into(), + iid: "11111111-1111-1111-1111-111111111111".into(), + }; + let unknown = TypeMeta::RuntimeClass { + namespace: "Contoso".into(), + name: "NotGenerated".into(), + default_interface: None, + }; + let context = PythonProjectionContext::standalone([ + widget.type_identity(), + interface.type_identity(), + ]) + .unwrap(); + let widgets = TypeMeta::Parameterized { + namespace: "Windows.Foundation.Collections".into(), + name: "IVectorView`1".into(), + piid: crate::codegen::winrt::python::collections::IVECTOR_VIEW_PIID.into(), + args: vec![widget.clone()], + }; + let stub = AnnotationSurface::Stub; + let async_of = |typ: &TypeMeta| TypeMeta::AsyncOperation(Box::new(typ.clone())); + let method = |raw_name: &str, params: Vec, return_type: TypeMeta| MethodMeta { + name: raw_name.into(), + raw_name: raw_name.into(), + params, + return_type: Some(return_type), + ..Default::default() + }; + + assert_eq!(returned(&widget, stub, &context), "Widget"); + assert_eq!(returned(&interface, stub, &context), "IWidget"); + assert_eq!(returned(&unknown, stub, &context), "DynWinRTValue"); + assert_eq!( + returned(&TypeMeta::Array(Box::new(widget.clone())), stub, &context), + "list[Widget | None]" + ); + assert_eq!( + returned(&async_of(&widget), stub, &context), + "WinRTCoroutine[Widget]" + ); assert_eq!( - py_return_type_safe(Some(&interface), &context), - "IWidget | None" + returned(&async_of(&widgets), stub, &context), + "WinRTCoroutine[Sequence[Widget | None]]" ); assert_eq!( - py_return_type_safe(Some(&TypeMeta::Object), &context), + returned(&TypeMeta::Object, stub, &context), "DynWinRTValue | None" ); + let getter = method("get_Widget", vec![], widget.clone()); + assert_eq!(py_property_type(&getter, &widget, stub, &context), "Widget"); + let documented_getter = MethodMeta { + documented_null_result: true, + ..getter.clone() + }; assert_eq!( - py_array_return_type(&runtime_class, &context), + py_property_type(&documented_getter, &widget, stub, &context), + "Widget | None" + ); + assert_eq!( + py_collection_item_type(&widget, ElementContainer::View, stub, &context), + "Widget | None" + ); + assert_eq!( + py_collection_item_type(&widget, ElementContainer::Mutable, stub, &context), + "Widget | None" + ); + assert_eq!( + py_collection_base_type( + CollectionKind::Sequence, + std::slice::from_ref(&widget), + stub, + &context + ), + Some("Sequence[Widget | None]".to_string()) + ); + assert_eq!( + py_collection_base_type( + CollectionKind::MutableMapping, + &[TypeMeta::String, widget.clone()], + stub, + &context + ), + Some("MutableMapping[str, Widget | None]".to_string()) + ); + assert_eq!( + py_collection_base_type( + CollectionKind::MutableMapping, + &[widget.clone(), TypeMeta::Object], + stub, + &context + ), + Some("MutableMapping[Widget | None, DynWinRTValue | None]".to_string()) + ); + assert_eq!( + py_collection_base_type( + CollectionKind::MutableMapping, + &[TypeMeta::Guid, TypeMeta::Object], + stub, + &context + ), + Some("MutableMapping[UUID, DynWinRTValue | None]".to_string()) + ); + let vector = |piid: &str| TypeMeta::Parameterized { + namespace: "Windows.Foundation.Collections".into(), + name: "IVector`1".into(), + piid: piid.into(), + args: vec![widget.clone()], + }; + for piid in [ + crate::codegen::winrt::python::collections::IVECTOR_PIID, + crate::codegen::winrt::python::collections::IOBSERVABLE_VECTOR_PIID, + ] { + assert_eq!( + returned(&vector(piid), stub, &context), + "MutableSequence[Widget | None]" + ); + } + let get_many = MethodMeta { + params: vec![ParamMeta { + name: "items".into(), + typ: TypeMeta::Array(Box::new(widget.clone())), + direction: ParamDirection::OutFill, + }], + return_type: Some(TypeMeta::U32), + element_access: Some(crate::meta::ElementAccess::Mutable), + ..method("GetMany", vec![], TypeMeta::U32) + }; + assert_eq!( + py_method_return_type(&get_many, stub, &context), "list[Widget | None]" ); + + let get_item = method("GetItemAsync", vec![], async_of(&widget)); + let try_get_item = method("TryGetItemAsync", vec![], async_of(&widget)); + let try_get_items = method("TryGetItemsAsync", vec![], async_of(&widgets)); + let try_parse = method( + "TryParse", + vec![ + ParamMeta { + name: "input".into(), + typ: TypeMeta::String, + direction: ParamDirection::In, + }, + ParamMeta { + name: "result".into(), + typ: widget.clone(), + direction: ParamDirection::Out, + }, + ], + TypeMeta::Bool, + ); + for (method, stub_type, runtime_type) in [ + ( + &get_item, + "WinRTCoroutine[Widget]", + "WinRTCoroutine[Widget | None]", + ), + ( + &try_get_item, + "WinRTCoroutine[Widget | None]", + "WinRTCoroutine[Widget | None]", + ), + ( + &try_get_items, + "WinRTCoroutine[Sequence[Widget | None] | None]", + "WinRTCoroutine[Sequence[Widget | None] | None]", + ), + ( + &try_parse, + "tuple[Widget | None, bool]", + "tuple[Widget | None, bool]", + ), + ] { + assert_eq!(py_method_return_type(method, stub, &context), stub_type); + assert_eq!( + py_method_return_type(method, AnnotationSurface::Runtime, &context), + runtime_type + ); + } + + let create = method("CreateWidget", vec![], widget.clone()); + let try_create = method("TryCreateWidget", vec![], widget.clone()); + for surface in [AnnotationSurface::Runtime, stub] { + assert_eq!( + py_factory_return_type("Widget", &create, surface, &context), + "'Widget'" + ); + } + assert_eq!( + py_factory_return_type("Widget", &try_create, stub, &context), + "Widget | None" + ); + assert_eq!( + py_factory_return_type("Widget", &try_create, AnnotationSurface::Runtime, &context), + "'Widget'" + ); } #[test] @@ -793,16 +1372,58 @@ mod tests { .type_identity() .with_kind(crate::types::TypeIdentityKind::Delegate)]) .unwrap(); + let method = MethodMeta { + params: vec![param], + ..Default::default() + }; assert_eq!( - py_param_list(&[¶m], &context), + py_method_param_list(&method, &context), "handler: Callable[..., object] | 'DynWinRTValue | DynWinRtDelegate'" ); assert_eq!( - py_runtime_param_list(&[¶m], &context), + py_runtime_method_param_list(&method, &context), "handler: Callable[..., object] | DynWinRTValue | DynWinRtDelegate" ); } + #[test] + fn runtime_method_params_compose_collection_contracts_and_delegate_annotations() { + let handler = TypeMeta::Interface { + namespace: "Test".into(), + name: "Handler".into(), + iid: "00000000-0000-0000-0000-000000000000".into(), + }; + let method = MethodMeta { + params: vec![ + ParamMeta { + name: "value".into(), + typ: TypeMeta::Object, + direction: ParamDirection::In, + }, + ParamMeta { + name: "handler".into(), + typ: handler.clone(), + direction: ParamDirection::In, + }, + ], + collection_inputs: vec![(0, CollectionInputRole::Value)], + ..Default::default() + }; + let context = PythonProjectionContext::standalone([handler + .type_identity() + .with_kind(crate::types::TypeIdentityKind::Delegate)]) + .unwrap(); + + assert_eq!( + py_method_param_list(&method, &context), + "value: DynWinRTValue | _DynWinRTObject | None, handler: Callable[..., object] | 'DynWinRTValue | DynWinRtDelegate'" + ); + assert_eq!( + py_runtime_method_param_list(&method, &context), + "value: DynWinRTValue | _DynWinRTObject | None, handler: Callable[..., object] | DynWinRTValue | DynWinRtDelegate" + ); + } + #[test] fn ireference_returns_are_projected_as_optional_values() { let reference = TypeMeta::Parameterized { @@ -812,10 +1433,12 @@ mod tests { args: vec![TypeMeta::U32], }; - assert_eq!( - py_return_type_safe(Some(&reference), &PythonProjectionContext::default()), - "int | None" - ); + for surface in [AnnotationSurface::Runtime, AnnotationSurface::Stub] { + assert_eq!( + returned(&reference, surface, &PythonProjectionContext::default()), + "int | None" + ); + } assert_eq!( py_param_type_safe(&reference, &PythonProjectionContext::default()), "int | None | IReference_UInt32" diff --git a/tools/dynwinrt-codegen/src/documented_nulls.rs b/tools/dynwinrt-codegen/src/documented_nulls.rs new file mode 100644 index 00000000..83117a13 --- /dev/null +++ b/tools/dynwinrt-codegen/src/documented_nulls.rs @@ -0,0 +1,324 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Windows SDK members whose documented result can be null. +//! +//! WinRT metadata carries no nullability. `api-docs/windows-null-results.txt` +//! lists the doc comment IDs (`M:`/`P:` api-ids) of the Windows SDK methods +//! and properties whose documentation says the result can be null; +//! `scripts/extract-null-results.py` derives it from MicrosoftDocs/winrt-api. +//! A member is looked up by the type its documentation lists it under, its +//! CLR name, and its parameter types. + +use std::collections::HashSet; +use std::sync::LazyLock; + +use crate::meta::MethodMeta; +use crate::types::TypeMeta; + +const TABLE: &str = include_str!("../api-docs/windows-null-results.txt"); + +struct Table { + members: HashSet, + owners: HashSet, +} + +static DOCUMENTED: LazyLock = LazyLock::new(|| { + let members = entries().map(normalize_api_id).collect::>(); + let owners = members.iter().filter_map(|id| owner_of(id)).collect(); + Table { members, owners } +}); + +/// The api-ids listed in the table, verbatim. +pub(crate) fn entries() -> impl Iterator { + TABLE + .lines() + .map(str::trim) + .filter(|line| !line.is_empty() && !line.starts_with('#')) +} + +/// Whether the documentation of `method`, listed under the type named +/// `owner` (`Namespace.Type`), says its result can be null. +pub(crate) fn documents_null_result(owner: &str, method: &MethodMeta) -> bool { + DOCUMENTED.owners.contains(owner) + && member_id(owner, method).is_some_and(|id| DOCUMENTED.members.contains(&id)) +} + +/// The normalized doc comment ID of `method` listed under `owner`. Only +/// methods and property getters produce results. +pub(crate) fn member_id(owner: &str, method: &MethodMeta) -> Option { + if method.is_property_getter { + let property = method.raw_name.strip_prefix("get_")?; + return Some(format!("P:{owner}.{property}")); + } + if method.is_property_setter || method.is_event_add || method.is_event_remove { + return None; + } + let parameters = method + .params + .iter() + .map(|parameter| doc_type_name(¶meter.typ)) + .collect::>() + .join(","); + Some(format!("M:{owner}.{}({parameters})", method.raw_name)) +} + +/// Normalizes an api-id so that metadata can reproduce it: methods always +/// carry a parameter list, and parameter types lose by-reference markers, +/// modifiers and generic arguments. +pub(crate) fn normalize_api_id(api_id: &str) -> String { + let Some(open) = api_id.find('(') else { + return if api_id.starts_with("M:") { + format!("{api_id}()") + } else { + api_id.to_string() + }; + }; + let parameters = split_parameters(api_id[open + 1..].trim_end_matches(')')) + .into_iter() + .map(normalize_doc_type) + .collect::>() + .join(","); + format!("{}({parameters})", &api_id[..open]) +} + +fn owner_of(id: &str) -> Option { + let member = id.get(2..)?.split('(').next()?; + member.rsplit_once('.').map(|(owner, _)| owner.to_string()) +} + +fn split_parameters(list: &str) -> Vec<&str> { + let mut parameters = Vec::new(); + let (mut depth, mut start) = (0usize, 0usize); + for (index, character) in list.char_indices() { + match character { + '{' => depth += 1, + '}' => depth = depth.saturating_sub(1), + ',' if depth == 0 => { + parameters.push(&list[start..index]); + start = index + 1; + } + _ => {} + } + } + if !list.is_empty() { + parameters.push(&list[start..]); + } + parameters +} + +fn normalize_doc_type(name: &str) -> String { + let name = name.split('!').next().unwrap_or(name).trim_end_matches('@'); + let mut result = String::with_capacity(name.len()); + let mut depth = 0usize; + for character in name.chars() { + match character { + '{' => depth += 1, + '}' => depth = depth.saturating_sub(1), + _ if depth == 0 => result.push(character), + _ => {} + } + } + match result.split_once('`') { + Some((definition, _)) => definition.to_string(), + // Some pages spell this struct with its .NET projection. + None if result == "System.Type" => "Windows.UI.Xaml.Interop.TypeName".to_string(), + None => result, + } +} + +/// The doc comment ID name of a parameter type, after normalization. +fn doc_type_name(typ: &TypeMeta) -> String { + let name = match typ { + TypeMeta::Bool => "System.Boolean", + TypeMeta::I8 => "System.SByte", + TypeMeta::U8 => "System.Byte", + TypeMeta::I16 => "System.Int16", + TypeMeta::U16 => "System.UInt16", + TypeMeta::I32 => "System.Int32", + TypeMeta::U32 => "System.UInt32", + TypeMeta::I64 => "System.Int64", + TypeMeta::U64 => "System.UInt64", + TypeMeta::F32 => "System.Single", + TypeMeta::F64 => "System.Double", + TypeMeta::Char16 => "System.Char", + TypeMeta::String => "System.String", + TypeMeta::Guid => "System.Guid", + TypeMeta::Object => "System.Object", + TypeMeta::AsyncAction => "Windows.Foundation.IAsyncAction", + TypeMeta::AsyncActionWithProgress(_) => "Windows.Foundation.IAsyncActionWithProgress", + TypeMeta::AsyncOperation(_) => "Windows.Foundation.IAsyncOperation", + TypeMeta::AsyncOperationWithProgress(..) => { + "Windows.Foundation.IAsyncOperationWithProgress" + } + TypeMeta::Array(inner) => return format!("{}[]", doc_type_name(inner)), + TypeMeta::Interface { + namespace, name, .. + } + | TypeMeta::RuntimeClass { + namespace, name, .. + } + | TypeMeta::Delegate { + namespace, name, .. + } + | TypeMeta::Struct { + namespace, name, .. + } + | TypeMeta::Enum { + namespace, name, .. + } + | TypeMeta::Parameterized { + namespace, name, .. + } => { + let definition = name.split('`').next().unwrap_or(name); + return format!("{namespace}.{definition}"); + } + }; + name.to_string() +} + +#[cfg(test)] +mod tests { + use std::collections::{BTreeMap, BTreeSet}; + use std::path::Path; + + use super::*; + use crate::meta::{ParamDirection, ParamMeta}; + + const WINDOWS_WINMD: &str = + r"C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.26100.0\Windows.winmd"; + + #[test] + fn api_ids_normalize_to_metadata_reproducible_keys() { + assert_eq!( + normalize_api_id("M:Windows.Devices.Sensors.Compass.GetDefault"), + "M:Windows.Devices.Sensors.Compass.GetDefault()" + ); + assert_eq!( + normalize_api_id( + "M:N.T.Find(Windows.Foundation.Collections.IMap{System.String,Windows.Foundation.Collections.IVector{System.String}},System.Byte[]@,System.Guid@!System.Runtime.CompilerServices.IsConst)" + ), + "M:N.T.Find(Windows.Foundation.Collections.IMap,System.Byte[],System.Guid)" + ); + assert_eq!(normalize_api_id("P:N.T.Value"), "P:N.T.Value"); + assert_eq!( + normalize_api_id("M:N.T.Get(System.Type)"), + normalize_api_id("M:N.T.Get(Windows.UI.Xaml.Interop.TypeName)") + ); + assert_eq!(owner_of("M:N.T.Find()").as_deref(), Some("N.T")); + } + + #[test] + fn member_ids_use_clr_names_and_parameter_types() { + let method = MethodMeta { + name: "GetDefaultWithAccelerometerReadingType".into(), + raw_name: "GetDefault".into(), + params: vec![ + ParamMeta { + name: "readingType".into(), + typ: TypeMeta::Enum { + namespace: "Windows.Devices.Sensors".into(), + name: "AccelerometerReadingType".into(), + underlying: Box::new(TypeMeta::I32), + members: vec![], + is_flags: false, + doc: None, + deprecated: None, + }, + direction: ParamDirection::In, + }, + ParamMeta { + name: "values".into(), + typ: TypeMeta::Array(Box::new(TypeMeta::Parameterized { + namespace: "Windows.Foundation.Collections".into(), + name: "IIterable`1".into(), + piid: String::new(), + args: vec![TypeMeta::String], + })), + direction: ParamDirection::Out, + }, + ], + ..Default::default() + }; + assert_eq!( + member_id("Windows.Devices.Sensors.Accelerometer", &method).as_deref(), + Some( + "M:Windows.Devices.Sensors.Accelerometer.GetDefault(Windows.Devices.Sensors.AccelerometerReadingType,Windows.Foundation.Collections.IIterable[])" + ) + ); + let getter = MethodMeta { + name: "get_Parent".into(), + raw_name: "get_Parent".into(), + is_property_getter: true, + ..Default::default() + }; + assert_eq!( + member_id("Windows.UI.Xaml.FrameworkElement", &getter).as_deref(), + Some("P:Windows.UI.Xaml.FrameworkElement.Parent") + ); + let setter = MethodMeta { + name: "put_Parent".into(), + raw_name: "put_Parent".into(), + is_property_setter: true, + ..Default::default() + }; + assert_eq!(member_id("N.T", &setter), None); + } + + /// Every entry names a member of the Windows SDK metadata whose parsed + /// method carries the documented-null fact. This guards the table against + /// typos and drift, and the key derivation against metadata changes. + #[test] + fn every_entry_resolves_to_a_flagged_windows_sdk_member() { + // Documented, but absent from the 10.0.26100 SDK metadata: newer APIs, + // and AllJoyn, which the SDK dropped. + const ABSENT_FROM_TEST_SDK: [&str; 3] = [ + "M:Windows.Devices.AllJoyn.AllJoynServiceInfo.FromIdAsync(System.String)", + "M:Windows.Gaming.UI.GameMonitor.GetDefault()", + "M:Windows.System.User.GetUserAgeRangeAsync()", + ]; + if !Path::new(WINDOWS_WINMD).is_file() { + eprintln!("Skipping: Windows.winmd not found"); + return; + } + let index = crate::meta::load_index(WINDOWS_WINMD).expect("Windows.winmd index"); + let mut by_owner = BTreeMap::>::new(); + for entry in entries() { + let id = normalize_api_id(entry); + let owner = owner_of(&id).expect("owner"); + by_owner.entry(owner).or_default().insert(id); + } + let mut unresolved = Vec::new(); + let mut unflagged = Vec::new(); + for (owner, ids) in &by_owner { + let (namespace, name) = owner.rsplit_once('.').expect("qualified owner"); + let methods = crate::meta::documented_owner_methods(&index, namespace, name); + for id in ids { + let matches = methods + .iter() + .filter(|method| member_id(owner, method).as_deref() == Some(id)) + .collect::>(); + if ABSENT_FROM_TEST_SDK.contains(&id.as_str()) { + let member = id[2..] + .split('(') + .next() + .unwrap() + .rsplit('.') + .next() + .unwrap(); + assert!( + !methods.iter().any(|method| method.raw_name == member), + "{id} is present in the test SDK; drop it from ABSENT_FROM_TEST_SDK" + ); + } else if matches.is_empty() { + unresolved.push(id.clone()); + } else if !matches.iter().any(|method| method.documented_null_result) { + unflagged.push(id.clone()); + } + } + } + assert!(unresolved.is_empty(), "unresolved entries: {unresolved:#?}"); + assert!(unflagged.is_empty(), "entries not applied: {unflagged:#?}"); + assert!(by_owner.values().map(BTreeSet::len).sum::() > 900); + } +} diff --git a/tools/dynwinrt-codegen/src/lib.rs b/tools/dynwinrt-codegen/src/lib.rs index ef0c3b86..09db795e 100644 --- a/tools/dynwinrt-codegen/src/lib.rs +++ b/tools/dynwinrt-codegen/src/lib.rs @@ -5,6 +5,7 @@ pub mod codegen; mod com_activation_registry; pub mod com_metadata; mod contract_registry; +mod documented_nulls; pub mod meta; pub mod types; mod win32_contracts; diff --git a/tools/dynwinrt-codegen/src/meta.rs b/tools/dynwinrt-codegen/src/meta.rs index 93e51ee8..126f5039 100644 --- a/tools/dynwinrt-codegen/src/meta.rs +++ b/tools/dynwinrt-codegen/src/meta.rs @@ -57,6 +57,177 @@ pub struct MethodMeta { pub param_docs: std::collections::HashMap, /// XML `` text. pub returns_doc: Option, + /// The Windows SDK documentation says the result can be null: a method's + /// return value (for asynchronous methods, the completed result) or a + /// property's value. See `documented_nulls`. + pub documented_null_result: bool, + /// Set on the members that read elements of the + /// `Windows.Foundation.Collections` interface declaring them. + pub element_access: Option, + /// Input parameters that represent collection elements, map keys, or map + /// values. Python uses this to validate and wrap `None` at the collection + /// boundary without changing general WinRT parameter conversion. + pub collection_inputs: Vec<(usize, CollectionInputRole)>, + /// Logical output indexes whose native contract permits a null reference + /// independently of general result nullability. `IMapView.Split` uses this + /// for its two optional map-view halves. + pub contract_nullable_outputs: Vec, +} + +/// How a member reads the elements of the `Windows.Foundation.Collections` +/// interface declaring it: `GetAt`, `GetMany`, `Lookup`, `Current`, `Key` and +/// `Value`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ElementAccess { + /// `IIterator`, `IVectorView`, `IMapView` and `IKeyValuePair.Value`. + ReadOnly, + /// `IVector` and `IMap`, in which anyone can store null. + Mutable, + /// The key of an `IKeyValuePair`. Whether null is valid depends on the + /// declared key ABI type. + MapKey, +} + +/// The role of an input parameter on a collection interface. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CollectionInputRole { + Element, + Key, + Value, +} + +fn collection_element_access( + namespace: &str, + definition: &str, + member: &str, +) -> Option { + if namespace != WINDOWS_FOUNDATION_COLLECTIONS_NAMESPACE { + return None; + } + let access = match (definition, member) { + ("IKeyValuePair`2", "get_Key") => return Some(ElementAccess::MapKey), + ("IKeyValuePair`2", "get_Value") => return Some(ElementAccess::ReadOnly), + ("IIterator`1" | "IVectorView`1" | "IMapView`2", _) => ElementAccess::ReadOnly, + ("IVector`1" | "IMap`2", _) => ElementAccess::Mutable, + _ => return None, + }; + matches!( + member, + "GetAt" | "GetMany" | "Lookup" | "get_Current" | "get_Key" | "get_Value" + ) + .then_some(access) +} + +fn collection_input_roles( + namespace: &str, + definition: &str, + member: &str, +) -> Vec<(usize, CollectionInputRole)> { + if namespace != WINDOWS_FOUNDATION_COLLECTIONS_NAMESPACE { + return Vec::new(); + } + use CollectionInputRole::{Element, Key, Value}; + match (definition, member) { + ("IVector`1" | "IVectorView`1", "IndexOf") => vec![(0, Element)], + ("IVector`1", "SetAt" | "InsertAt") => vec![(1, Element)], + ("IVector`1", "Append" | "ReplaceAll") => vec![(0, Element)], + ("IMap`2" | "IMapView`2", "Lookup" | "HasKey") | ("IMap`2", "Remove") => vec![(0, Key)], + ("IMap`2", "Insert") => vec![(0, Key), (1, Value)], + _ => Vec::new(), + } +} + +fn collection_nullable_output_indices( + namespace: &str, + definition: &str, + member: &str, +) -> Vec { + if namespace == WINDOWS_FOUNDATION_COLLECTIONS_NAMESPACE + && definition == "IMapView`2" + && member == "Split" + { + vec![0, 1] + } else { + Vec::new() + } +} + +#[cfg(test)] +mod collection_contract_tests { + use super::*; + + #[test] + fn map_view_split_alone_has_two_optional_outputs() { + assert_eq!( + collection_nullable_output_indices( + WINDOWS_FOUNDATION_COLLECTIONS_NAMESPACE, + "IMapView`2", + "Split" + ), + vec![0, 1] + ); + for (definition, member) in [ + ("IMapView`2", "Lookup"), + ("IMap`2", "Split"), + ("IVectorView`1", "Split"), + ] { + assert!( + collection_nullable_output_indices( + WINDOWS_FOUNDATION_COLLECTIONS_NAMESPACE, + definition, + member + ) + .is_empty() + ); + } + } + + #[test] + fn map_key_inputs_cover_mutable_and_view_operations() { + for (definition, member) in [ + ("IMap`2", "Lookup"), + ("IMap`2", "HasKey"), + ("IMap`2", "Remove"), + ("IMapView`2", "Lookup"), + ("IMapView`2", "HasKey"), + ] { + assert_eq!( + collection_input_roles( + WINDOWS_FOUNDATION_COLLECTIONS_NAMESPACE, + definition, + member + ), + vec![(0, CollectionInputRole::Key)] + ); + } + assert_eq!( + collection_input_roles(WINDOWS_FOUNDATION_COLLECTIONS_NAMESPACE, "IMap`2", "Insert"), + vec![ + (0, CollectionInputRole::Key), + (1, CollectionInputRole::Value) + ] + ); + } + + #[test] + fn key_value_pair_key_and_value_have_distinct_element_contracts() { + assert_eq!( + collection_element_access( + WINDOWS_FOUNDATION_COLLECTIONS_NAMESPACE, + "IKeyValuePair`2", + "get_Key" + ), + Some(ElementAccess::MapKey) + ); + assert_eq!( + collection_element_access( + WINDOWS_FOUNDATION_COLLECTIONS_NAMESPACE, + "IKeyValuePair`2", + "get_Value" + ), + Some(ElementAccess::ReadOnly) + ); + } } /// A WinRT interface with its methods. @@ -1224,6 +1395,8 @@ fn parse_class_from_index(index: &reader::Index, namespace: &str, name: &str) -> let mut ancestor_key: Option<(String, String)> = def .extends() .map(|e| (e.namespace().to_string(), e.name().to_string())); + // The documentation lists inherited members under the class declaring them. + let mut documentation_owners = vec![full_name.clone()]; while let Some((ext_ns, ext_name)) = ancestor_key.take() { if ext_ns == "System" && ext_name == "Object" { break; @@ -1232,6 +1405,7 @@ fn parse_class_from_index(index: &reader::Index, namespace: &str, name: &str) -> Some(d) => d, None => break, }; + documentation_owners.push(format!("{ext_ns}.{ext_name}")); for iface_impl in parent_def.interface_impls() { let iface_ty = iface_impl.interface(&[]); if iface_impl.has_attribute("OverridableAttribute") { @@ -1378,6 +1552,19 @@ fn parse_class_from_index(index: &reader::Index, namespace: &str, name: &str) -> } } + for interface in default_interface + .iter_mut() + .chain(required_interfaces.iter_mut()) + .chain(factory_interfaces.iter_mut()) + .chain(static_interfaces.iter_mut()) + { + for method in &mut interface.methods { + method.documented_null_result |= documentation_owners + .iter() + .any(|owner| crate::documented_nulls::documents_null_result(owner, method)); + } + } + Some(ClassMeta { name: name.to_string(), namespace: namespace.to_string(), @@ -1428,6 +1615,36 @@ fn parse_interface(index: &reader::Index, namespace: &str, name: &str) -> Option parse_interface_methods(index, &def, name, namespace, &iid, &[]) } +/// The parsed methods that the documentation lists under `namespace.name`: +/// the members of a class's interfaces, or an interface's own members. +#[cfg(test)] +pub(crate) fn documented_owner_methods( + index: &reader::Index, + namespace: &str, + name: &str, +) -> Vec { + let Some(def) = index.get(namespace, name).next() else { + return Vec::new(); + }; + if def.extends().is_none() { + return parse_interface(index, namespace, name) + .map(|interface| interface.methods) + .unwrap_or_default(); + } + parse_class_from_index(index, namespace, name) + .map(|class| { + class + .default_interface + .into_iter() + .chain(class.required_interfaces) + .chain(class.factory_interfaces) + .chain(class.static_interfaces) + .flat_map(|interface| interface.methods) + .collect() + }) + .unwrap_or_default() +} + fn parse_interface_type( index: &reader::Index, interface_type: &windows_metadata::Type, @@ -1582,6 +1799,9 @@ fn parse_interface_methods( ) -> Option { let winmd_generics: Vec = generic_args.iter().map(type_meta_to_winmd_type).collect(); + // The documentation lists interface members under the interface + // definition, e.g. `Windows.Foundation.Collections.IVector`1`. + let documentation_owner = format!("{namespace}.{}", def.name()); let mut methods = Vec::new(); let mut implementation_metadata = InterfaceImplementationMetadata { @@ -1741,7 +1961,11 @@ fn parse_interface_methods( format!("({})", clr_sig_types.join(",")) }; - methods.push(MethodMeta { + let element_access = collection_element_access(namespace, def.name(), &raw_name); + let collection_inputs = collection_input_roles(namespace, def.name(), &raw_name); + let contract_nullable_outputs = + collection_nullable_output_indices(namespace, def.name(), &raw_name); + let mut method_meta = MethodMeta { name: method_name.clone(), vtable_index, params, @@ -1756,7 +1980,14 @@ fn parse_interface_methods( deprecated: None, param_docs: std::collections::HashMap::new(), returns_doc: None, - }); + documented_null_result: false, + element_access, + collection_inputs, + contract_nullable_outputs, + }; + method_meta.documented_null_result = + crate::documented_nulls::documents_null_result(&documentation_owner, &method_meta); + methods.push(method_meta); } let (generic_piid, generic_args_vec) = if !generic_args.is_empty() { diff --git a/tools/dynwinrt-codegen/tests/implementation_naming_test.rs b/tools/dynwinrt-codegen/tests/implementation_naming_test.rs index 16bd74e5..02a888a9 100644 --- a/tools/dynwinrt-codegen/tests/implementation_naming_test.rs +++ b/tools/dynwinrt-codegen/tests/implementation_naming_test.rs @@ -1116,10 +1116,10 @@ fn python_full_identity_collision_imports_named_peer_not_generic_self() { from pyviews.{peer_module} import {peer_name} as Peer\n\ from pyviews.{foreign_module} import {foreign_name} as Foreign\n\ def check(box: Box, peer: Peer, foreign: Foreign) -> None:\n\ - \x20 assert_type(box.echo_self(box), Box | None)\n\ - \x20 assert_type(box.echo_peer(peer), Peer | None)\n\ - \x20 assert_type(box.echo_foreign(foreign), Foreign | None)\n\ - \x20 assert_type(peer.echo_self(peer), Peer | None)\n" + \x20 assert_type(box.echo_self(box), Box)\n\ + \x20 assert_type(box.echo_peer(peer), Peer)\n\ + \x20 assert_type(box.echo_foreign(foreign), Foreign)\n\ + \x20 assert_type(peer.echo_self(peer), Peer)\n" ), ); typecheck_py_package(&fixture.0); diff --git a/tools/dynwinrt-codegen/tests/observable_vector_test.rs b/tools/dynwinrt-codegen/tests/observable_vector_test.rs index 63d436df..821c47bd 100644 --- a/tools/dynwinrt-codegen/tests/observable_vector_test.rs +++ b/tools/dynwinrt-codegen/tests/observable_vector_test.rs @@ -121,11 +121,11 @@ fn observable_vector_projects_python_mutable_sequence_and_typed_events() { py.contains("_dynwinrt_symbol('i_vector_object', 'IVector_Object')._set_native(self, obj)") ); assert!(py.contains("self._observable_obj = obj.cast(IID_IObservableVector_Object)")); - let create_signature = "def create(items: Iterable['DynWinRTValue | _DynWinRTObject']) -> 'IObservableVector_Object':"; + let create_signature = "def create(items: Iterable[DynWinRTValue | _DynWinRTObject | None]) -> 'IObservableVector_Object':"; assert!(py.contains(create_signature), "{py}"); assert!( py.contains( - "_dynwinrt_new_vector(items, lambda item: getattr(item, '_obj', item), DynWinRTType.object())" + "_dynwinrt_new_vector(items, lambda item: _dynwinrt_collection_item(item, lambda item: getattr(item, '_obj', item), True, 'collection element'), DynWinRTType.object())" ), "{py}" ); diff --git a/tools/dynwinrt-codegen/tests/python_collection_helper_collision_test.rs b/tools/dynwinrt-codegen/tests/python_collection_helper_collision_test.rs new file mode 100644 index 00000000..6bc676be --- /dev/null +++ b/tools/dynwinrt-codegen/tests/python_collection_helper_collision_test.rs @@ -0,0 +1,445 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; +use std::sync::atomic::{AtomicU64, Ordering}; + +use dynwinrt_codegen::codegen::python::{self, PythonProjectionContext}; +use dynwinrt_codegen::meta::{self, ClassMeta, InterfaceMeta}; +use dynwinrt_codegen::types::{TypeIdentity, TypeIdentityKind}; +use windows_metadata::{MethodCallAttributes, Signature, Type, TypeAttributes, TypeName, writer}; + +const WINDOWS_WINMD: &str = + r"C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.26100.0\Windows.winmd"; +const COLLIDING: &str = "_dynwinrt_collection_item"; +const HELPER_ALIAS: &str = "_dynwinrt_collection_item_2"; +const COLLECTIONS: &str = "Windows.Foundation.Collections"; + +static NEXT: AtomicU64 = AtomicU64::new(0); + +struct Fixture(PathBuf); + +impl Fixture { + fn new() -> Self { + let directory = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("target") + .join(format!( + "python-collection-helper-collision-{}-{}", + std::process::id(), + NEXT.fetch_add(1, Ordering::Relaxed), + )); + fs::create_dir_all(&directory).unwrap(); + Self(directory) + } +} + +impl Drop for Fixture { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } +} + +fn python() -> PathBuf { + std::env::var_os("DYNWINRT_TEST_PYTHON") + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from("python")) +} + +fn success(output: Output) { + assert!( + output.status.success(), + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); +} + +fn runtime_available() -> bool { + let available = Command::new(python()) + .args([ + "-c", + "from dynwinrt import DynWinRTValue; assert hasattr(DynWinRTValue, 'create_vector')", + ]) + .output() + .is_ok_and(|output| output.status.success()); + assert!( + available || std::env::var("DYNWINRT_REQUIRE_IMPLEMENTATION_RUNTIME").as_deref() != Ok("1"), + "the collection-helper collision probe requires the current Python binding" + ); + if !available { + eprintln!("Skipping the collection-helper runtime probe; set DYNWINRT_TEST_PYTHON."); + } + available +} + +fn closed(namespace: &str, name: &str, generics: Vec) -> Type { + Type::Name(TypeName { + namespace: namespace.into(), + name: name.into(), + generics, + }) +} + +fn default_attribute(file: &mut writer::File, implementation: writer::InterfaceImpl) { + let attribute = file.TypeRef("Windows.Foundation.Metadata", "DefaultAttribute"); + let constructor = file.MemberRef( + ".ctor", + &Signature { + flags: MethodCallAttributes::HASTHIS, + return_type: Type::Void, + types: vec![], + }, + writer::MemberRefParent::TypeRef(attribute), + ); + file.Attribute( + writer::HasAttribute::InterfaceImpl(implementation), + writer::AttributeType::MemberRef(constructor), + &[], + ); +} + +fn collection_class(file: &mut writer::File, namespace: &str, interface: Type) { + let object = file.TypeRef("System", "Object"); + let class = file.TypeDef( + namespace, + COLLIDING, + writer::TypeDefOrRef::TypeRef(object), + TypeAttributes::Public | TypeAttributes::Sealed | TypeAttributes::WindowsRuntime, + ); + let implementation = file.InterfaceImpl(class, &interface); + default_attribute(file, implementation); +} + +fn collision_metadata(path: &Path) { + let mut file = writer::File::new("PythonCollectionHelperCollision"); + collection_class( + &mut file, + "Audit.Vector", + closed(COLLECTIONS, "IVector`1", vec![Type::Object]), + ); + collection_class( + &mut file, + "Audit.Map", + closed(COLLECTIONS, "IMap`2", vec![Type::Object, Type::Object]), + ); + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, file.into_stream()).unwrap(); +} + +fn metadata_set(custom: &Path) -> String { + format!("{};{WINDOWS_WINMD}", custom.display()) +} + +fn parse_classes(custom: &Path) -> Vec { + ["Audit.Vector", "Audit.Map"] + .into_iter() + .map(|namespace| { + meta::parse_class(&metadata_set(custom), namespace, COLLIDING) + .unwrap_or_else(|| panic!("parse {namespace}.{COLLIDING}")) + }) + .collect() +} + +fn class_identity(class: &ClassMeta) -> TypeIdentity { + TypeIdentity::named(TypeIdentityKind::Class, &class.namespace, &class.name) +} + +fn projection_context(classes: &[ClassMeta], packaged: bool) -> PythonProjectionContext { + let identities = classes.iter().map(class_identity).chain( + classes + .iter() + .flat_map(ClassMeta::all_interfaces) + .map(InterfaceMeta::type_identity), + ); + PythonProjectionContext::new(identities, packaged).unwrap() +} + +fn module_name(context: &PythonProjectionContext, namespace: &str) -> String { + context.implementation_module_for_named(TypeIdentityKind::Class, namespace, COLLIDING) +} + +fn assert_collision_safe(source: &str, operation: &str) { + assert!( + source.contains( + "from ._runtime import _dynwinrt_collection_item as _dynwinrt_collection_item_2" + ), + "{source}" + ); + assert!(source.contains(&format!("class {COLLIDING}(")), "{source}"); + assert!( + source.contains(&format!("{HELPER_ALIAS}(")), + "missing aliased helper in {operation}:\n{source}" + ); + assert!( + !source.contains("from ._runtime import _dynwinrt_collection_item\n"), + "{source}" + ); +} + +struct Modules { + vector_class: String, + map_class: String, +} + +fn modules(context: &PythonProjectionContext) -> Modules { + Modules { + vector_class: module_name(context, "Audit.Vector"), + map_class: module_name(context, "Audit.Map"), + } +} + +fn write_standalone(root: &Path, classes: &[ClassMeta]) -> Modules { + let package = root.join("pyviews"); + fs::create_dir_all(&package).unwrap(); + fs::write(package.join("__init__.py"), "").unwrap(); + fs::write( + package.join("_runtime.py"), + python::generate_runtime_support_module(), + ) + .unwrap(); + + let context = projection_context(classes, false); + for class in classes { + let module = context.implementation_module(&class_identity(class)); + let source = python::generate_class(&context, class, &Default::default()); + assert_collision_safe(&source, &module); + fs::write(package.join(format!("{module}.py")), source).unwrap(); + } + modules(&context) +} + +fn generate_packaged(root: &Path, custom: &Path, classes: &[ClassMeta]) -> Modules { + let package = root.join("pyviews"); + success( + Command::new(env!("CARGO_BIN_EXE_dynwinrt-codegen")) + .args(["generate", "--winmd"]) + .arg(custom) + .args(["--ref", WINDOWS_WINMD, "--output"]) + .arg(&package) + .args([ + "--lang", + "py", + "--class-name", + "Audit.Vector._dynwinrt_collection_item,Audit.Map._dynwinrt_collection_item", + ]) + .output() + .unwrap(), + ); + let context = projection_context(classes, true); + let modules = modules(&context); + for (module, operation) in [ + (&modules.vector_class, "vector operations"), + (&modules.map_class, "map operations"), + ] { + let source = fs::read_to_string(package.join(format!("{module}.py"))).unwrap(); + assert_collision_safe(&source, operation); + } + modules +} + +fn runtime_probe(root: &Path, modules: &Modules) { + fs::write( + root.join("probe.py"), + format!( + r#" +import importlib +import os +import dynwinrt as dw + +support = importlib.import_module("pyviews._runtime") +vector_module = importlib.import_module("pyviews.{vector_class}") +map_module = importlib.import_module("pyviews.{map_class}") +Vector = vector_module.{COLLIDING} +Map = map_module.{COLLIDING} + +if "DYNWINRT_EXPECT_OLD_COLLECTION_HELPER" not in os.environ: + for module in (vector_module, map_module): + assert module.{HELPER_ALIAS} is support._dynwinrt_collection_item + assert module.{COLLIDING} is not support._dynwinrt_collection_item + +with dw.RoApartment(1), dw.projected_lifetime_scope(): + live = dw.DynWinRTValue.activation_factory("Windows.Foundation.Uri") + + vector = Vector._from_native( + dw.DynWinRTValue.create_vector([], dw.DynWinRTType.object()) + ) + vector.append(None) + vector.append(live) + assert vector[0] is None + item = vector[1] + assert item is not None and item.identity_raw() == live.identity_raw() + item.release() + vector.replace_all([live, None]) + assert vector[1] is None + + mapping = Map._from_native( + dw.DynWinRTValue.create_map( + [], [], dw.DynWinRTType.object(), dw.DynWinRTType.object() + ) + ) + assert mapping.insert(None, None) is False + assert mapping.lookup(None) is None + assert mapping.insert(live, live) is False + value = mapping.lookup(live) + assert value is not None and value.identity_raw() == live.identity_raw() + value.release() + + live.release() + +print("collection-helper-collision-ok") +"#, + vector_class = modules.vector_class, + map_class = modules.map_class, + ), + ) + .unwrap(); + let output = Command::new(python()) + .args(["-B", "probe.py"]) + .current_dir(root) + .output() + .unwrap(); + assert!( + String::from_utf8_lossy(&output.stdout).contains("collection-helper-collision-ok"), + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + success(output); +} + +fn old_hardcoded_binding_fails(root: &Path, modules: &Modules) { + for module in [&modules.vector_class, &modules.map_class] { + let path = root.join("pyviews").join(format!("{module}.py")); + let source = fs::read_to_string(&path).unwrap(); + let old = source + .replace( + "from ._runtime import _dynwinrt_collection_item as _dynwinrt_collection_item_2", + "from ._runtime import _dynwinrt_collection_item", + ) + .replace("_dynwinrt_collection_item_2(", "_dynwinrt_collection_item("); + fs::write(path, old).unwrap(); + } + let output = Command::new(python()) + .args(["-B", "probe.py"]) + .current_dir(root) + .env("DYNWINRT_EXPECT_OLD_COLLECTION_HELPER", "1") + .output() + .unwrap(); + assert!( + !output.status.success(), + "old hardcoded helper unexpectedly passed" + ); + assert!( + String::from_utf8_lossy(&output.stderr).contains("TypeError"), + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); +} + +fn strict_typecheck(root: &Path, modules: &Modules) { + let available = Command::new(python()) + .args(["-m", "mypy", "--version"]) + .output() + .is_ok_and(|output| output.status.success()); + assert!( + available || std::env::var("DYNWINRT_REQUIRE_MYPY").as_deref() != Ok("1"), + "DYNWINRT_REQUIRE_MYPY=1 but mypy is unavailable" + ); + if !available { + eprintln!("Skipping collection-helper strict typing: mypy unavailable."); + return; + } + let consumer = format!( + r#" +from dynwinrt import DynWinRTValue +from pyviews.{vector_class} import {COLLIDING} as Vector +from pyviews.{map_class} import {COLLIDING} as Map + +def vectors(vector: Vector, value: DynWinRTValue) -> None: + vector.append(None) + vector.append(value) + vector.replace_all([None, value]) + +def maps(mapping: Map, value: DynWinRTValue) -> None: + mapping.insert(None, None) + mapping.insert(value, value) +"#, + vector_class = modules.vector_class, + map_class = modules.map_class, + ); + fs::write(root.join("typing_probe.py"), &consumer).unwrap(); + let binding_stubs = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join("bindings") + .join("py") + .canonicalize() + .unwrap(); + success( + Command::new(python()) + .args([ + "-B", + "-m", + "mypy", + "--strict", + "--follow-imports=silent", + "--no-incremental", + "--cache-dir", + "mypy-cache", + "typing_probe.py", + ]) + .current_dir(root) + .env( + "MYPYPATH", + std::env::join_paths([binding_stubs, root.to_path_buf()]).unwrap(), + ) + .output() + .unwrap(), + ); + if let Some(pyright) = std::env::var_os("DYNWINRT_PYRIGHT") { + fs::write( + root.join("pyright_probe.py"), + format!("# pyright: strict, reportPrivateUsage=false\n{consumer}"), + ) + .unwrap(); + success( + Command::new(pyright) + .args(["--pythonpath"]) + .arg(python()) + .arg("pyright_probe.py") + .current_dir(root) + .output() + .unwrap(), + ); + } +} + +#[test] +fn collection_helper_aliases_execute_for_packaged_and_standalone_outputs() { + if !Path::new(WINDOWS_WINMD).is_file() { + eprintln!("Skipping collection-helper collision test: Windows.winmd unavailable."); + return; + } + let fixture = Fixture::new(); + let custom = fixture.0.join("metadata").join("Collision.winmd"); + collision_metadata(&custom); + let classes = parse_classes(&custom); + assert_eq!(classes.len(), 2); + + let standalone = fixture.0.join("standalone"); + let modules = write_standalone(&standalone, &classes); + if runtime_available() { + runtime_probe(&standalone, &modules); + old_hardcoded_binding_fails(&standalone, &modules); + } + + let packaged = fixture.0.join("packaged"); + let modules = generate_packaged(&packaged, &custom, &classes); + strict_typecheck(&packaged, &modules); + if runtime_available() { + runtime_probe(&packaged, &modules); + } +} diff --git a/tools/dynwinrt-codegen/tests/python_constructor_boundary_test.rs b/tools/dynwinrt-codegen/tests/python_constructor_boundary_test.rs index 1bb07f47..e3b4316f 100644 --- a/tools/dynwinrt-codegen/tests/python_constructor_boundary_test.rs +++ b/tools/dynwinrt-codegen/tests/python_constructor_boundary_test.rs @@ -72,7 +72,7 @@ fn system_returned_class_keeps_only_internal_native_wrapping() { assert!(!py.contains("self._set_native(type(self).create(")); assert!(!py.contains("_IActivationFactory =")); assert!(pyi.contains("def __init__(self, _not_constructible: NoReturn) -> None: ...")); - assert!(pyi.contains("def get_current() -> SystemResult | None: ...")); + assert!(pyi.contains("def get_current() -> SystemResult: ...")); assert!(!pyi.contains("def from_value("), "{pyi}"); assert!(!pyi.contains("def __init__(self, obj: DynWinRTValue)")); assert!(!pyi.contains("def __init__(self)")); diff --git a/tools/dynwinrt-codegen/tests/python_consumer_typing_test.rs b/tools/dynwinrt-codegen/tests/python_consumer_typing_test.rs index 98ad13ee..837e732c 100644 --- a/tools/dynwinrt-codegen/tests/python_consumer_typing_test.rs +++ b/tools/dynwinrt-codegen/tests/python_consumer_typing_test.rs @@ -138,6 +138,31 @@ fn typecheck(fixture: &Fixture, packages: &[&str], consumer: &str, errors: &[&st } } +fn pyright_typecheck(fixture: &Fixture, consumer: &str, expected_errors: usize) { + let Some(pyright) = std::env::var_os("DYNWINRT_PYRIGHT") else { + return; + }; + fs::write( + fixture.0.join("pyright_consumer.py"), + format!("# pyright: strict\n{consumer}"), + ) + .unwrap(); + let output = Command::new(pyright) + .args(["--pythonpath"]) + .arg(python()) + .arg("pyright_consumer.py") + .current_dir(&fixture.0) + .output() + .unwrap(); + let text = diagnostics(&output); + let actual = text + .lines() + .filter(|line| line.contains(" - error: ")) + .count(); + assert_eq!(actual, expected_errors, "{text}"); + assert_eq!(output.status.success(), expected_errors == 0, "{text}"); +} + fn parameter(typ: TypeMeta) -> ParamMeta { ParamMeta { name: "value".into(), @@ -227,6 +252,39 @@ fn generate_fixture(fixture: &Fixture, package: &str) { params: vec![parameter(TypeMeta::Array(Box::new(TypeMeta::Object)))], ..Default::default() }, + MethodMeta { + name: "GetResources".into(), + raw_name: "GetResources".into(), + vtable_index: 10, + return_type: Some(TypeMeta::Array(Box::new(TypeMeta::RuntimeClass { + namespace: "Contoso".into(), + name: "Resource".into(), + default_interface: Some(Box::new(TypeMeta::Interface { + namespace: "Contoso".into(), + name: "IItem".into(), + iid: "00000001-1111-1111-1111-111111111111".into(), + })), + }))), + ..Default::default() + }, + MethodMeta { + name: "GetCounts".into(), + raw_name: "GetCounts".into(), + vtable_index: 11, + return_type: Some(TypeMeta::Array(Box::new(TypeMeta::I32))), + ..Default::default() + }, + MethodMeta { + name: "GetItems".into(), + raw_name: "GetItems".into(), + vtable_index: 12, + return_type: Some(TypeMeta::Array(Box::new(TypeMeta::Interface { + namespace: "Contoso".into(), + name: "IItem".into(), + iid: "00000001-1111-1111-1111-111111111111".into(), + }))), + ..Default::default() + }, ], ); let classes = [ @@ -252,7 +310,7 @@ fn generate_fixture(fixture: &Fixture, package: &str) { "IVector_Resource", "IVector`1", "913337e9-11a1-4345-a3a2-4e7f956e222d", - vec![resource_type], + vec![resource_type.clone()], ), ( "IMap_Object_Object", @@ -260,6 +318,24 @@ fn generate_fixture(fixture: &Fixture, package: &str) { "3c2925fe-8519-45c1-aa79-197b6718c1c1", vec![TypeMeta::Object, TypeMeta::Object], ), + ( + "IMap_String_Object", + "IMap`2", + "3c2925fe-8519-45c1-aa79-197b6718c1c1", + vec![TypeMeta::String, TypeMeta::Object], + ), + ( + "IMap_Resource_Resource", + "IMap`2", + "3c2925fe-8519-45c1-aa79-197b6718c1c1", + vec![resource_type.clone(), resource_type], + ), + ( + "IMap_Guid_Object", + "IMap`2", + "3c2925fe-8519-45c1-aa79-197b6718c1c1", + vec![TypeMeta::Guid, TypeMeta::Object], + ), ] { let methods = if definition == "IVector`1" { vec![ @@ -281,6 +357,30 @@ fn generate_fixture(fixture: &Fixture, package: &str) { ..Default::default() }, ] + } else if definition == "IMap`2" { + vec![MethodMeta { + name: "Insert".into(), + raw_name: "Insert".into(), + vtable_index: 10, + params: vec![ + ParamMeta { + name: "key".into(), + typ: args[0].clone(), + direction: ParamDirection::In, + }, + ParamMeta { + name: "value".into(), + typ: args[1].clone(), + direction: ParamDirection::In, + }, + ], + return_type: Some(TypeMeta::Bool), + collection_inputs: vec![ + (0, dynwinrt_codegen::meta::CollectionInputRole::Key), + (1, dynwinrt_codegen::meta::CollectionInputRole::Value), + ], + ..Default::default() + }] } else { Vec::new() }; @@ -430,7 +530,7 @@ def collections(resource: Resource, derived: OtherDerived, raw: DynWinRTValue, typecheck( &fixture, &["first", "second"], - r#"from dynwinrt import DynWinRTValue + r#"from dynwinrt import DynWinRTValue, DynWinRtDelegate from first.contoso__i_item import IItem from first.contoso__i_unrelated import IUnrelated from first.contoso__resource import Resource, ResourceLike @@ -498,6 +598,184 @@ def invalid_collections(resource: Resource, unrelated: Unrelated, "[assignment]", ], ); + typecheck( + &fixture, + &["first"], + r#"from typing import assert_type +from dynwinrt import DynWinRTValue +from first.contoso__resource import Resource +from first.windows__foundation__collections__i_map_object_object import IMap_Object_Object +from first.windows__foundation__collections__i_map_resource_resource import IMap_Resource_Resource + +def nullable_reference_keys( + objects: IMap_Object_Object, resources: IMap_Resource_Resource +) -> None: + objects[None] = None + assert_type(objects[None], DynWinRTValue | None) + resources[None] = None + resources.update({None: None}) + assert_type(resources.setdefault(None, None), Resource | None) + assert_type(resources[None], Resource | None) +"#, + &[], + ); + pyright_typecheck( + &fixture, + r#"from typing import assert_type +from dynwinrt import DynWinRTValue +from first.contoso__resource import Resource +from first.windows__foundation__collections__i_map_object_object import IMap_Object_Object +from first.windows__foundation__collections__i_map_resource_resource import IMap_Resource_Resource + +def nullable_reference_keys( + objects: IMap_Object_Object, resources: IMap_Resource_Resource +) -> None: + objects[None] = None + assert_type(objects[None], DynWinRTValue | None) + resources[None] = None + resources.update({None: None}) + assert_type(resources.setdefault(None, None), Resource | None) + assert_type(resources[None], Resource | None) +"#, + 0, + ); + typecheck( + &fixture, + &["first"], + r#"from first.windows__foundation__collections__i_map_guid_object import IMap_Guid_Object +from first.windows__foundation__collections__i_map_string_object import IMap_String_Object +from uuid import UUID + +def invalid_key(guid: IMap_Guid_Object, string: IMap_String_Object) -> None: + guid[None] = None + guid.get(None) + guid.update({None: None}) + guid.setdefault(None, None) + string[None] = None + string.get(None) + string.update({None: None}) + string.setdefault(None, None) +"#, + &[ + "[index]", + "[call-overload]", + "[dict-item]", + "[call-overload]", + "[index]", + "[call-overload]", + "[dict-item]", + "[call-overload]", + ], + ); + pyright_typecheck( + &fixture, + r#"from first.windows__foundation__collections__i_map_guid_object import IMap_Guid_Object +from first.windows__foundation__collections__i_map_string_object import IMap_String_Object + +def invalid_key(guid: IMap_Guid_Object, string: IMap_String_Object) -> None: + guid[None] = None + guid.get(None) + guid.update({None: None}) + guid.setdefault(None, None) + string[None] = None + string.get(None) + string.update({None: None}) + string.setdefault(None, None) +"#, + 12, + ); +} + +#[test] +fn reference_array_results_preserve_null_elements() { + if !has_mypy() { + return; + } + let fixture = Fixture::new(); + generate_fixture(&fixture, "views"); + let source = fs::read_to_string(fixture.0.join("views").join("contoso__content.py")).unwrap(); + assert!(source.contains("def get_resources(self) -> list[Resource | None]:")); + assert!(source.contains("_dynwinrt_wrap_values('contoso__resource', 'Resource'")); + assert!(source.contains("def get_items(self) -> list[IItem | None]:")); + assert!(source.contains("_dynwinrt_wrap_values('contoso__i_item', 'IItem'")); + assert!(source.contains("def get_counts(self) -> list[int]:")); + let valid = r#"from typing import assert_type +from views.contoso__content import Content +from views.contoso__i_item import IItem +from views.contoso__resource import Resource + +def consume(content: Content) -> list[str]: + resources = content.get_resources() + assert_type(resources, list[Resource | None]) + assert_type(content.get_items(), list[IItem | None]) + assert_type(content.get_counts(), list[int]) + return [resource.name for resource in resources if resource is not None] +"#; + typecheck(&fixture, &["views"], valid, &[]); + pyright_typecheck(&fixture, valid, 0); + + let invalid = r#"from views.contoso__content import Content + +def consume(content: Content) -> str: + return content.get_resources()[0].name +"#; + typecheck(&fixture, &["views"], invalid, &["[union-attr]"]); + pyright_typecheck(&fixture, invalid, 1); + + if has_implementation_runtime() { + let script = r#"from dynwinrt import RoApartment, project_as, projected_lifetime_scope +from views.contoso__content import Content +from views.contoso__i_content import IContent + +class ContentHandlers: + def get_content(self): + return None + + def set_content(self, _value): + pass + + def set_object(self, _value): + pass + + def set_objects(self, _value): + pass + + def get_resources(self): + return [None] + + def get_counts(self): + return [1, 2] + + def get_items(self): + return [None] + +with RoApartment(1), projected_lifetime_scope(): + with IContent.implement(ContentHandlers()) as owner: + content = project_as(owner.value, Content) + resources = content.get_resources() + assert isinstance(resources, list) + assert resources == [None] + items = content.get_items() + assert isinstance(items, list) + assert items == [None] + counts = content.get_counts() + assert isinstance(counts, list) + assert counts == [1, 2] +print("nullable-reference-array-native-ok", flush=True) +"#; + fs::write(fixture.0.join("reference_array_runtime.py"), script).unwrap(); + let output = Command::new(python()) + .args(["-B", "reference_array_runtime.py"]) + .current_dir(&fixture.0) + .output() + .unwrap(); + assert!(output.status.success(), "{}", diagnostics(&output)); + assert!( + String::from_utf8_lossy(&output.stdout).contains("nullable-reference-array-native-ok"), + "{}", + diagnostics(&output) + ); + } } #[test] @@ -531,7 +809,7 @@ fn real_windows_consumers_accept_file_stream_content_and_composition_instances() &fixture, &["sdk"], r#"from typing import assert_type -from dynwinrt import DynWinRTValue, DynWinRtDelegate +from dynwinrt import DynWinRTValue from sdk.windows.storage import FileIO, StorageFile from sdk.windows.media.playback import MediaPlayer from sdk.windows.media.speech_synthesis import SpeechSynthesisStream @@ -913,10 +1191,7 @@ fn map_changed_handlers_receive_typed_observable_maps_and_arguments() { .output() .unwrap(); assert!(output.status.success(), "{}", diagnostics(&output)); - typecheck( - &fixture, - &["sdk"], - r#"from typing import assert_type + let valid = r#"from typing import assert_type from dynwinrt import DynWinRTValue, DynWinRtDelegate from sdk.windows.foundation.collections import ( CollectionChange, IMapChangedEventArgs_String, IObservableMap_String_Object, @@ -932,10 +1207,14 @@ def typed( def on_properties( sender: IObservableMap_String_Object, args: IMapChangedEventArgs_String ) -> None: + assert_type(sender, IObservableMap_String_Object) + assert_type(args, IMapChangedEventArgs_String) assert_type(len(sender), int) assert_type(sender[args.key], DynWinRTValue | None) + assert_type(next(iter(sender.values())), DynWinRTValue | None) assert_type(args.collection_change, CollectionChange) + assert_type(properties.lookup("key"), DynWinRTValue | None) properties.subscribe_map_changed(on_properties) properties.once_map_changed( lambda sender, args: assert_type(sender, IObservableMap_String_Object) @@ -950,13 +1229,11 @@ def typed( lambda sender, args: assert_type(sender, IObservableMap_String_String) ) strings.subscribe_map_changed(lambda sender, args: assert_type(sender[args.key], str)) -"#, - &[], - ); - typecheck( - &fixture, - &["sdk"], - r#"from dynwinrt import DynWinRtDelegate +"#; + typecheck(&fixture, &["sdk"], valid, &[]); + pyright_typecheck(&fixture, valid, 0); + + let invalid = r#"from dynwinrt import DynWinRtDelegate from sdk.windows.foundation.collections import PropertySet, StringMap def wrong_sender(sender: int, args: object) -> None: ... @@ -972,7 +1249,11 @@ def untyped( properties.on_map_changed(lambda sender, args: args.index) strings.subscribe_map_changed(lambda sender, args: sender[0]) strings.once_map_changed(native) -"#, +"#; + typecheck( + &fixture, + &["sdk"], + invalid, &[ "[arg-type]", "[arg-type]", @@ -981,41 +1262,86 @@ def untyped( "[arg-type]", ], ); + pyright_typecheck(&fixture, invalid, 7); + if has_implementation_runtime() { fs::write( fixture.0.join("map_events_runtime.py"), - r#"from dynwinrt import RoApartment, projected_lifetime_scope + r#"from collections.abc import Callable +from typing import get_type_hints + +from dynwinrt import ( + DynWinRTValue, DynWinRtDelegate, RoApartment, projected_lifetime_scope, +) from sdk.windows.foundation import PropertyValue from sdk.windows.foundation.collections import ( CollectionChange, IMapChangedEventArgs_String, IObservableMap_String_Object, IObservableMap_String_String, PropertySet, StringMap, ) +callback_annotation = ( + Callable[..., object] | DynWinRTValue | DynWinRtDelegate +) +assert get_type_hints(PropertySet.on_map_changed)["callback"] == callback_annotation +assert get_type_hints(PropertySet.subscribe_map_changed)["callback"] == callback_annotation +assert get_type_hints(PropertySet.once_map_changed)["callback"] == Callable[..., object] + with RoApartment(1), projected_lifetime_scope(): - for collection, sender_type, values in ( - (PropertySet(), IObservableMap_String_Object, - (PropertyValue.create_int32(1), PropertyValue.create_int32(2))), - (StringMap(), IObservableMap_String_String, ("first", "second")), - ): - changes = [] - - def handler(sender, args): - assert isinstance(sender, sender_type), type(sender) - assert isinstance(args, IMapChangedEventArgs_String), type(args) - changes.append((args.collection_change, args.key, len(sender), "k" in sender)) - - unsubscribe = collection.subscribe_map_changed(handler) - collection["k"] = values[0] - collection["k"] = values[1] - del collection["k"] - unsubscribe() - collection["k"] = values[0] - assert changes == [ - (CollectionChange.ItemInserted, "k", 1, True), - (CollectionChange.ItemChanged, "k", 1, True), - (CollectionChange.ItemRemoved, "k", 0, False), - ], changes -print("map-changed-native-ok", flush=True) + properties = PropertySet() + changes = [] + + def on_properties(sender, args): + assert isinstance(sender, IObservableMap_String_Object), type(sender) + assert isinstance(args, IMapChangedEventArgs_String), type(args) + value = sender[args.key] + looked_up = sender.lookup(args.key) + assert (looked_up is None) == (value is None) + if value is not None: + assert looked_up.identity_raw() == value.identity_raw() + looked_up.release() + listed = list(sender.values()) + assert (listed[-1] is None) == (value is None) + changes.append(( + type(sender).__name__, + type(args).__name__, + type(value).__name__ if value is not None else None, + value is None, + )) + if listed[-1] is not None: + listed[-1].release() + if value is not None: + value.release() + + unsubscribe = properties.subscribe_map_changed(on_properties) + properties["null"] = None + properties["null"] = PropertyValue.create_int32(1) + unsubscribe() + assert changes[0] == ( + "IObservableMap_String_Object", + "IMapChangedEventArgs_String", + None, + True, + ), changes + assert changes[1][0:2] == ( + "IObservableMap_String_Object", + "IMapChangedEventArgs_String", + ), changes + assert changes[1][2:] == ("DynWinRTValue", False), changes + + strings = StringMap() + string_changes = [] + stop = strings.subscribe_map_changed( + lambda sender, args: string_changes.append( + (type(sender).__name__, type(args).__name__, sender[args.key]) + ) + ) + strings["k"] = "value" + stop() + assert string_changes == [ + ("IObservableMap_String_String", "IMapChangedEventArgs_String", "value") + ], string_changes + +print("map-changed-native-ok", changes, string_changes, flush=True) "#, ) .unwrap(); @@ -1025,8 +1351,580 @@ print("map-changed-native-ok", flush=True) .output() .unwrap(); assert!(output.status.success(), "{}", diagnostics(&output)); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("map-changed-native-ok"), "{stdout}"); + assert!( + stdout.contains( + "('IObservableMap_String_Object', 'IMapChangedEventArgs_String', None, True)" + ), + "{stdout}" + ); + assert!(stdout.contains("'DynWinRTValue', False"), "{stdout}"); + } +} + +#[test] +fn natural_sdk_consumers_guard_only_nullable_results() { + let winmd = Path::new( + r"C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.26100.0\Windows.winmd", + ); + if !winmd.is_file() || !has_mypy() { + eprintln!("Skipping natural SDK consumers: Windows.winmd or mypy unavailable."); + return; + } + let fixture = Fixture::new(); + let output = Command::new(env!("CARGO_BIN_EXE_dynwinrt-codegen")) + .args(["generate", "--winmd"]) + .arg(winmd) + .args([ + "--class-name", + "Windows.Foundation.Uri,Windows.Foundation.Collections.PropertySet,\ + Windows.Data.Json.JsonObject,Windows.Globalization.Calendar,\ + Windows.Security.Cryptography.CryptographicBuffer,\ + Windows.Security.Cryptography.Core.HashAlgorithmProvider,\ + Windows.Storage.StorageFolder,Windows.Storage.FileIO,\ + Windows.Storage.Streams.DataReader,Windows.Storage.Streams.DataWriter,\ + Windows.Storage.Streams.InMemoryRandomAccessStream,\ + Windows.Devices.Sensors.Accelerometer", + "--lang", + "py", + "--output", + ]) + .arg(fixture.0.join("sdk")) + .output() + .unwrap(); + assert!(output.status.success(), "{}", diagnostics(&output)); + let imports = r#"from collections.abc import Sequence +from typing import assert_type +from dynwinrt import DynWinRTValue, WinRTCoroutine +from sdk.windows.data.json import IJsonValue, JsonObject +from sdk.windows.devices.sensors import Accelerometer +from sdk.windows.foundation import Uri +from sdk.windows.foundation.collections import PropertySet +from sdk.windows.globalization import Calendar +from sdk.windows.security.cryptography import CryptographicBuffer +from sdk.windows.security.cryptography.core import HashAlgorithmProvider +from sdk.windows.storage import CreationCollisionOption, FileIO, IStorageItem, StorageFile, StorageFolder +from sdk.windows.storage.streams import DataReader, DataWriter, InMemoryRandomAccessStream +"#; + typecheck( + &fixture, + &["sdk"], + &format!( + r#"{imports} +def uri_demo() -> str: + uri = Uri("https://example.com/a/b?x=1&y=two") + query = {{ + entry.name: entry.value + for entry in uri.query_parsed + if entry is not None + }} + return uri.combine_uri("c/d").absolute_uri + str(query) + +def json_demo() -> list[str]: + parsed = JsonObject.parse('{{"tags": ["a", "b"]}}') + assert_type(JsonObject.try_parse("{{}}"), tuple[JsonObject | None, bool]) + tags = parsed.get_named_array("tags") + assert_type(tags[0], IJsonValue | None) + return [value.get_string() for value in tags if value is not None] + +def sensor_demo() -> float | None: + accelerometer = Accelerometer.get_default() + if accelerometer is None: + return None + reading = accelerometer.get_current_reading() + return None if reading is None else reading.acceleration_x + +def calendar_demo(calendar: Calendar) -> str: + languages: Sequence[str] = calendar.languages + return languages[0] + +def crypto_demo(data: bytes) -> str: + buffer = CryptographicBuffer.create_from_byte_array(data) + digest = HashAlgorithmProvider.open_algorithm("SHA256").hash_data(buffer) + return CryptographicBuffer.encode_to_hex_string(digest) + +def object_values(properties: PropertySet) -> DynWinRTValue | None: + assert_type(properties["count"], DynWinRTValue | None) + return properties.lookup("count") + +async def streams_demo() -> str: + stream = InMemoryRandomAccessStream() + writer = DataWriter(stream.get_output_stream_at(0)) + writer.write_string("streamed text") + written = await writer.store_async() + reader = DataReader(stream.get_input_stream_at(0)) + return reader.read_string(await reader.load_async(written)) + +async def storage_demo(path: str) -> list[str]: + folder = await StorageFolder.get_folder_from_path_async(path) + file = await folder.create_file_async("notes.txt", CreationCollisionOption.ReplaceExisting) + await FileIO.write_text_async(file, "first line") + assert_type(folder.create_file_async("a.txt"), WinRTCoroutine[StorageFile]) + assert_type(folder.try_get_item_async("notes.txt"), WinRTCoroutine[IStorageItem | None]) + assert_type(folder.get_parent_async(), WinRTCoroutine[StorageFolder | None]) + return [item.name for item in await folder.get_files_async() if item is not None] +"# + ), + &[], + ); +} + +#[test] +fn mutable_collection_mutators_accept_none() { + let winmd = Path::new( + r"C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.26100.0\Windows.winmd", + ); + if !winmd.is_file() || !has_mypy() { + eprintln!("Skipping mutable collection mutators: Windows.winmd or mypy unavailable."); + return; + } + let fixture = Fixture::new(); + let output = Command::new(env!("CARGO_BIN_EXE_dynwinrt-codegen")) + .args(["generate", "--winmd"]) + .arg(winmd) + .args([ + "--class-name", + "Windows.Storage.StorageLibrary,Windows.Data.Json.JsonObject,\ + Windows.Foundation.Collections.StringMap,Windows.Foundation.Uri,\ + Windows.Foundation.Collections.PropertySet,Windows.UI.Xaml.ResourceDictionary", + "--lang", + "py", + "--output", + ]) + .arg(fixture.0.join("sdk")) + .output() + .unwrap(); + assert!(output.status.success(), "{}", diagnostics(&output)); + + let stringable = TypeMeta::Interface { + namespace: "Windows.Foundation".into(), + name: "IStringable".into(), + iid: "96369f54-8eb6-48f0-abce-c1b211e627c3".into(), + }; + let stringable_map_type = TypeMeta::Parameterized { + namespace: "Windows.Foundation.Collections".into(), + name: "IMap`2".into(), + piid: "3c2925fe-8519-45c1-aa79-197b6718c1c1".into(), + args: vec![stringable.clone(), stringable.clone()], + }; + let stringable_map = InterfaceMeta { + namespace: "Windows.Foundation.Collections".into(), + name: "IMap_IStringable_IStringable".into(), + generic_name: Some("IMap`2".into()), + generic_piid: Some("3c2925fe-8519-45c1-aa79-197b6718c1c1".into()), + generic_args: vec![stringable.clone(), stringable.clone()], + methods: vec![ + MethodMeta { + name: "Lookup".into(), + raw_name: "Lookup".into(), + vtable_index: 6, + params: vec![parameter(stringable.clone())], + return_type: Some(stringable.clone()), + element_access: Some(dynwinrt_codegen::meta::ElementAccess::Mutable), + collection_inputs: vec![(0, dynwinrt_codegen::meta::CollectionInputRole::Key)], + ..Default::default() + }, + MethodMeta { + name: "get_Size".into(), + raw_name: "get_Size".into(), + vtable_index: 7, + is_property_getter: true, + return_type: Some(TypeMeta::U32), + ..Default::default() + }, + MethodMeta { + name: "HasKey".into(), + raw_name: "HasKey".into(), + vtable_index: 8, + params: vec![parameter(stringable.clone())], + return_type: Some(TypeMeta::Bool), + collection_inputs: vec![(0, dynwinrt_codegen::meta::CollectionInputRole::Key)], + ..Default::default() + }, + MethodMeta { + name: "GetView".into(), + raw_name: "GetView".into(), + vtable_index: 9, + return_type: Some(TypeMeta::Object), + ..Default::default() + }, + MethodMeta { + name: "Insert".into(), + raw_name: "Insert".into(), + vtable_index: 10, + params: vec![ + ParamMeta { + name: "key".into(), + typ: stringable.clone(), + direction: ParamDirection::In, + }, + ParamMeta { + name: "value".into(), + typ: stringable.clone(), + direction: ParamDirection::In, + }, + ], + return_type: Some(TypeMeta::Bool), + collection_inputs: vec![ + (0, dynwinrt_codegen::meta::CollectionInputRole::Key), + (1, dynwinrt_codegen::meta::CollectionInputRole::Value), + ], + ..Default::default() + }, + MethodMeta { + name: "Remove".into(), + raw_name: "Remove".into(), + vtable_index: 11, + params: vec![parameter(stringable.clone())], + collection_inputs: vec![(0, dynwinrt_codegen::meta::CollectionInputRole::Key)], + ..Default::default() + }, + MethodMeta { + name: "Clear".into(), + raw_name: "Clear".into(), + vtable_index: 12, + ..Default::default() + }, + ], + ..Default::default() + }; + let stringable_context = python::PythonProjectionContext::new( + [ + stringable.type_identity(), + stringable_map_type.type_identity(), + ], + true, + ) + .unwrap(); + let stringable_module = stringable_context.implementation_module_for_interface(&stringable_map); + fs::write( + fixture + .0 + .join("sdk") + .join(format!("{stringable_module}.py")), + python::generate_interface(&stringable_context, &stringable_map), + ) + .unwrap(); + fs::write( + fixture + .0 + .join("sdk") + .join(format!("{stringable_module}.pyi")), + python_stub::generate_interface_stub(&stringable_context, &stringable_map), + ) + .unwrap(); + let object_map_stub = fs::read_to_string( + fixture + .0 + .join("sdk") + .join("windows__foundation__collections__i_map_object_object.pyi"), + ) + .unwrap(); + assert!(object_map_stub.contains("MutableMapping[DynWinRTValue | None, DynWinRTValue | None]")); + let pair_stub = fs::read_to_string( + fixture + .0 + .join("sdk") + .join("windows__foundation__collections__i_key_value_pair_object_object.pyi"), + ) + .unwrap(); + assert!(pair_stub.contains("def key(self) -> DynWinRTValue | None: ...")); + assert!(pair_stub.contains("def value(self) -> DynWinRTValue | None: ...")); + // Inherited MutableSequence and MutableMapping mutators take the element + // type of the collection base, which keeps `| None` for mutable + // collections, like the generated item setters. + typecheck( + &fixture, + &["sdk"], + r#"from typing import assert_type +from dynwinrt import DynWinRTValue +from sdk.windows.data.json import IJsonValue, JsonObject +from sdk.windows.foundation import IStringable, Uri +from sdk.windows.foundation.collections import ( + IMap_Object_Object, + IMap_String_String, + IObservableVector_StorageFolder, +) +from sdk.windows.storage import StorageFolder +from sdk.windows__foundation__collections__property_set import IMap_String_Object +from sdk.windows__foundation__collections__i_map_i_stringable_i_stringable import ( + IMap_IStringable_IStringable, +) + +def vector(folders: IObservableVector_StorageFolder) -> None: + IObservableVector_StorageFolder.create([None]) + folders.append(None) + folders.extend([None]) + folders.insert(0, None) + folders[0] = None + assert_type(folders[0], StorageFolder | None) + +def mapping(values: JsonObject) -> None: + values.update({"k": None}) + values.setdefault("k", None) + values["k"] = None + assert_type(values["k"], IJsonValue | None) + +def object_values(values: IMap_String_Object, uri: Uri) -> None: + values["none"] = None + values["uri"] = uri + assert_type(values["none"], DynWinRTValue | None) + +def reference_keys( + objects: IMap_Object_Object, stringable: IMap_IStringable_IStringable +) -> None: + objects[None] = None + stringable[None] = None + assert_type(objects[None], DynWinRTValue | None) + assert_type(stringable[None], IStringable | None) + +def string_keys(values: IMap_String_String) -> None: + values["key"] = "value" + values.get("key") +"#, + &[], + ); + + if has_implementation_runtime() { + // Nested IReference collection wrappers call the local boxing + // helper. Generate an otherwise synthetic module to prove recursive + // helper detection emits it; the runtime script below imports and + // invokes the emitted helper. A scalar collection is the negative + // control and must not emit it. + let reference = TypeMeta::Parameterized { + namespace: "Windows.Foundation".into(), + name: "IReference`1".into(), + piid: "61c17706-2d65-11e0-9ae8-d48564015472".into(), + args: vec![TypeMeta::I32], + }; + let iterable = TypeMeta::Parameterized { + namespace: "Windows.Foundation.Collections".into(), + name: "IIterable`1".into(), + piid: "faa585ea-6214-4217-afda-7f46de5869b3".into(), + args: vec![reference.clone()], + }; + let mapping = TypeMeta::Parameterized { + namespace: "Windows.Foundation.Collections".into(), + name: "IMap`2".into(), + piid: "3c2925fe-8519-45c1-aa79-197b6718c1c1".into(), + args: vec![TypeMeta::String, reference.clone()], + }; + let nested = interface( + "INestedReferences", + 99, + vec![ + MethodMeta { + name: "SetValues".into(), + raw_name: "SetValues".into(), + params: vec![parameter(iterable.clone())], + ..Default::default() + }, + MethodMeta { + name: "SetMapping".into(), + raw_name: "SetMapping".into(), + params: vec![parameter(mapping.clone())], + ..Default::default() + }, + ], + ); + let nested_context = python::PythonProjectionContext::new( + [ + nested.type_identity(), + iterable.type_identity(), + mapping.type_identity(), + reference.type_identity(), + ], + true, + ) + .unwrap(); + let nested_module = nested_context.implementation_module_for_interface(&nested); + let nested_source = python::generate_interface(&nested_context, &nested); + assert!(nested_source.contains("def _dynwinrt_box_reference(value, value_type, wrap):")); + assert!(nested_source.contains("_dynwinrt_vector(value, lambda item:")); + assert!(nested_source.contains("_dynwinrt_map(value, lambda item:")); + assert!(nested_source.contains("_dynwinrt_box_reference(")); + fs::write( + fixture.0.join("sdk").join(format!("{nested_module}.py")), + nested_source, + ) + .unwrap(); + + let scalar = interface( + "IScalarCollection", + 100, + vec![MethodMeta { + name: "SetValues".into(), + raw_name: "SetValues".into(), + params: vec![parameter(TypeMeta::Parameterized { + namespace: "Windows.Foundation.Collections".into(), + name: "IIterable`1".into(), + piid: "faa585ea-6214-4217-afda-7f46de5869b3".into(), + args: vec![TypeMeta::I32], + })], + ..Default::default() + }], + ); + let scalar_source = python::generate_interface(&nested_context, &scalar); + assert!(!scalar_source.contains("def _dynwinrt_box_reference(value, value_type, wrap):")); + assert!(!scalar_source.contains("_dynwinrt_box_reference(")); + + let script = r#"from dynwinrt import ( + DynWinRTMethodSig, DynWinRTType, DynWinRTValue, RoApartment, WinGUID, + projected_lifetime_scope, +) +from sdk.windows.foundation.collections import ( + IIterable_StorageFolder, + IMap_Object_Object, + IMap_String_String, + IObservableVector_StorageFolder, + IVector_String, + IVector_StorageFolder, +) +from sdk.windows__foundation__collections__i_map_i_stringable_i_stringable import ( + IMap_IStringable_IStringable, +) +from sdk.windows__data__json__json_object import IID_IJsonValue, IMap_String_IJsonValue +from sdk.__NESTED_MODULE__ import _dynwinrt_box_reference + +with RoApartment(1), projected_lifetime_scope(): + boxed = _dynwinrt_box_reference( + 17, DynWinRTType.i32_type(), DynWinRTValue.from_i32 + ) + assert not boxed.is_null() + boxed.release() + null = _dynwinrt_box_reference( + None, DynWinRTType.i32_type(), DynWinRTValue.from_i32 + ) + assert null.is_null() + null.release() + + for vector_type in (IVector_StorageFolder, IObservableVector_StorageFolder): + vector = vector_type.create([None]) + assert vector[0] is None + vector.append(None) + vector.insert(0, None) + vector[1] = None + vector[1:2] = [None, None] + vector.extend([None]) + assert list(vector) == [None] * len(vector) + + base = vector.as_vector() if hasattr(vector, "as_vector") else vector + view = base.get_view() + assert view is not None + assert view[0] is None + assert list(view) == [None] * len(view) + iterator = base.as_interface(IIterable_StorageFolder).first() + assert iterator is not None + assert next(iterator) is None + + native = DynWinRTValue.create_map( + [], [], DynWinRTType.hstring(), DynWinRTType.interface(IID_IJsonValue) + ) + mapping = IMap_String_IJsonValue.from_value(native) + mapping.update({"update": None}) + assert mapping.setdefault("default", None) is None + mapping["index"] = None + assert mapping["update"] is None + assert mapping["default"] is None + assert mapping["index"] is None + assert list(mapping.values()) == [None, None, None] + + objects = IMap_Object_Object.create({None: None}) + assert None in objects + assert objects[None] is None + objects[None] = None + assert objects.setdefault(None, None) is None + assert next(iter(objects.items())) == (None, None) + assert dict(objects) == {None: None} + assert objects == {None: None} + try: + hash(objects) + except TypeError: + pass + else: + raise AssertionError("mutable maps must remain unhashable") + object_view = objects.get_view() + assert object_view is not None + assert object_view[None] is None + assert dict(object_view) == {None: None} + del objects[None] + assert None not in objects + + stringable = DynWinRTType.interface( + WinGUID.parse("96369F54-8EB6-48F0-ABCE-C1B211E627C3") + ) + stringables = IMap_IStringable_IStringable.create({None: None}) + assert None in stringables + assert stringables[None] is None + stringables[None] = None + stringable_pair = DynWinRTType.parameterized( + WinGUID.parse("02B51929-C1C4-4A7E-8940-0312B5C18500"), + [stringable, stringable], + ) + stringable_iterator = DynWinRTType.parameterized( + WinGUID.parse("6A79E863-4300-459A-9966-CBB660963EE1"), + [stringable_pair], + ) + iterable = DynWinRTType.register_interface( + "IIterable_IStringablePair", + DynWinRTType.parameterized( + WinGUID.parse("FAA585EA-6214-4217-AFDA-7F46DE5869B3"), + [stringable_pair], + ).iid(), + ).add_method("First", DynWinRTMethodSig().add_out(stringable_iterator)) + iterator = DynWinRTType.register_interface( + "IIterator_IStringablePair", stringable_iterator.iid() + ).add_method("get_Current", DynWinRTMethodSig().add_out(stringable_pair)) + pair = DynWinRTType.register_interface( + "IKeyValuePair_IStringable_IStringable", stringable_pair.iid() + ).add_method("get_Key", DynWinRTMethodSig().add_out(stringable)).add_method( + "get_Value", DynWinRTMethodSig().add_out(stringable) + ) + iterable_value = stringables._obj.cast(iterable.iid()) + iterator_value = iterable.method(6).invoke(iterable_value, []) + pair_value = iterator.method(6).invoke(iterator_value, []) + assert pair.method(6).invoke(pair_value, []).is_null() + assert pair.method(7).invoke(pair_value, []).is_null() + del stringables[None] + assert None not in stringables + + empty_string_map = IMap_String_String.create({}) + string_view = empty_string_map.get_view() + assert string_view is not None + assert string_view.split() == (None, None) + + strings = IMap_String_String.create({}) + invalid = ( + ("map key cannot be None", lambda: mapping.__setitem__(None, None)), + ("map value cannot be None", lambda: strings.__setitem__("key", None)), + ("map key cannot be None", lambda: IMap_String_String.create({None: "value"})), + ("map value cannot be None", lambda: IMap_String_String.create({"key": None})), + ("collection element cannot be None", lambda: IVector_String.create([None])), + ( + "collection element cannot be None", + lambda: IVector_String.create([]).append(None), + ), + ) + for expected, operation in invalid: + try: + operation() + except TypeError as error: + assert str(error) == expected + else: + raise AssertionError(f"{expected!r} was not raised") +print("nullable-collection-native-ok", flush=True) +"# + .replace("__NESTED_MODULE__", &nested_module); + fs::write(fixture.0.join("nullable_collections.py"), script).unwrap(); + let output = Command::new(python()) + .args(["-B", "nullable_collections.py"]) + .current_dir(&fixture.0) + .output() + .unwrap(); + assert!(output.status.success(), "{}", diagnostics(&output)); assert!( - String::from_utf8_lossy(&output.stdout).contains("map-changed-native-ok"), + String::from_utf8_lossy(&output.stdout).contains("nullable-collection-native-ok"), "{}", diagnostics(&output) ); @@ -1076,6 +1974,15 @@ class ContentHandlers: def set_objects(self, value: list[DynWinRTValue | None]) -> None: self.items = value + def get_resources(self): + return [] + + def get_counts(self): + return [] + + def get_items(self): + return [] + class WrongObject: _obj = 42 diff --git a/tools/dynwinrt-codegen/tests/python_delegate_callback_test.rs b/tools/dynwinrt-codegen/tests/python_delegate_callback_test.rs index 7690ed51..ed4576e7 100644 --- a/tools/dynwinrt-codegen/tests/python_delegate_callback_test.rs +++ b/tools/dynwinrt-codegen/tests/python_delegate_callback_test.rs @@ -14,6 +14,10 @@ use std::sync::atomic::{AtomicU64, Ordering}; use dynwinrt_codegen::meta::{ClassMeta, InterfaceMeta, MethodMeta, ParamDirection, ParamMeta}; use dynwinrt_codegen::types::{FieldMeta, TypeMeta}; +use windows_metadata::{ + MethodAttributes, MethodCallAttributes, MethodImplAttributes, ParamAttributes, Signature, Type, + TypeAttributes, Value, writer, +}; const WINDOWS_WINMD: &str = r"C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.26100.0\Windows.winmd"; @@ -75,6 +79,420 @@ fn class_wrapper(module: &str, class: &str, argument: &str) -> String { ) } +fn guid(file: &mut writer::File, definition: writer::TypeDef, id: u32) { + let attribute = file.TypeRef("Windows.Foundation.Metadata", "GuidAttribute"); + let constructor = file.MemberRef( + ".ctor", + &Signature { + flags: MethodCallAttributes::HASTHIS, + return_type: Type::Void, + types: vec![ + Type::U32, + Type::U16, + Type::U16, + Type::U8, + Type::U8, + Type::U8, + Type::U8, + Type::U8, + Type::U8, + Type::U8, + Type::U8, + ], + }, + writer::MemberRefParent::TypeRef(attribute), + ); + let values = [ + Value::U32(id), + Value::U16(0x6281), + Value::U16(0x4900), + Value::U8(0xb7), + Value::U8(0x82), + Value::U8(4), + Value::U8(3), + Value::U8(2), + Value::U8(1), + Value::U8(9), + Value::U8(0x10), + ] + .into_iter() + .map(|value| (String::new(), value)) + .collect::>(); + file.Attribute( + writer::HasAttribute::TypeDef(definition), + writer::AttributeType::MemberRef(constructor), + &values, + ); +} + +fn write_nested_delegate_metadata(path: &Path) { + let mut file = writer::File::new("NestedDelegateCallbacks"); + let base = file.TypeRef("System", "MulticastDelegate"); + for (name, id, argument) in [ + ("InnerHandler", 0x31d447a1, None), + ( + "OuterHandler", + 0x31d447a2, + Some(Type::named("Audit", "InnerHandler")), + ), + ] { + let definition = file.TypeDef( + "Audit", + name, + writer::TypeDefOrRef::TypeRef(base), + TypeAttributes::Public | TypeAttributes::Sealed | TypeAttributes::WindowsRuntime, + ); + guid(&mut file, definition, id); + file.MethodDef( + ".ctor", + &Signature { + flags: MethodCallAttributes::HASTHIS, + return_type: Type::Void, + types: vec![], + }, + MethodAttributes::Public | MethodAttributes::SpecialName, + MethodImplAttributes::default(), + ); + file.MethodDef( + "Invoke", + &Signature { + flags: MethodCallAttributes::HASTHIS, + return_type: Type::Void, + types: argument.iter().cloned().collect(), + }, + MethodAttributes::Public | MethodAttributes::Virtual | MethodAttributes::NewSlot, + MethodImplAttributes::default(), + ); + if argument.is_some() { + file.Param("inner", 1, ParamAttributes::In); + } + } + let emitter = file.TypeDef( + "Audit", + "IEmitter", + writer::TypeDefOrRef::default(), + TypeAttributes::Public + | TypeAttributes::Interface + | TypeAttributes::Abstract + | TypeAttributes::WindowsRuntime, + ); + guid(&mut file, emitter, 0x31d447a3); + file.MethodDef( + "SetHandler", + &Signature { + flags: MethodCallAttributes::HASTHIS, + return_type: Type::Void, + types: vec![Type::named("Audit", "OuterHandler")], + }, + MethodAttributes::Public + | MethodAttributes::Abstract + | MethodAttributes::Virtual + | MethodAttributes::NewSlot, + MethodImplAttributes::default(), + ); + file.Param("handler", 1, ParamAttributes::In); + fs::write(path, file.into_stream()).unwrap(); +} + +fn generate_nested_delegate_views(binary: &Path, metadata: &Path, generated: &Path) { + let output = Command::new(binary) + .args(["generate", "--winmd"]) + .arg(metadata) + .args(["--namespace", "Audit", "--lang", "py", "--output"]) + .arg(generated) + .output() + .unwrap(); + assert!( + output.status.success(), + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + +#[test] +fn nested_delegate_callback_argument_preserves_native_null() { + let root = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .join("target") + .join(format!( + "nd{}-{}", + std::process::id(), + NEXT.fetch_add(1, Ordering::Relaxed) + )); + fs::create_dir_all(&root).unwrap(); + let fixture = Output(root); + let metadata = fixture.0.join("Nested.winmd"); + write_nested_delegate_metadata(&metadata); + let generated = fixture.0.join("projected"); + generate_nested_delegate_views( + Path::new(env!("CARGO_BIN_EXE_dynwinrt-codegen")), + &metadata, + &generated, + ); + let stub = fs::read_to_string(generated.join("audit__i_emitter.pyi")).unwrap(); + let signature = stub + .lines() + .rev() + .find(|line| line.contains("def set_handler(")) + .expect("generated IEmitter.set_handler signature"); + assert_eq!( + signature, + " def set_handler(self, handler: Callable[[DynWinRTValue | None], object] | \ + 'DynWinRTValue | DynWinRtDelegate') -> None: ..." + ); + assert!( + !stub.contains("from .audit__inner_handler import IID_InnerHandler, InnerHandler"), + "{stub}" + ); + let runtime = fs::read_to_string(generated.join("audit__i_emitter.py")).unwrap(); + let null_projection = + "lambda __p0__: ((lambda value: None if value.is_null() else value)(__p0__),)"; + assert!(runtime.contains(null_projection), "{runtime}"); + + if let Some(main_codegen) = std::env::var_os("DYNWINRT_MAIN_CODEGEN") { + let baseline = fixture.0.join("main"); + generate_nested_delegate_views(Path::new(&main_codegen), &metadata, &baseline); + let main_stub = fs::read_to_string(baseline.join("audit__i_emitter.pyi")).unwrap(); + let main_signature = main_stub + .lines() + .rev() + .find(|line| line.contains("def set_handler(")) + .expect("main IEmitter.set_handler signature"); + assert_eq!(signature, main_signature); + let main_runtime = fs::read_to_string(baseline.join("audit__i_emitter.py")).unwrap(); + assert!(main_runtime.contains(null_projection), "{main_runtime}"); + eprintln!("#191 main and #190 IEmitter stubs: {signature}"); + } + + let python = std::env::var_os("DYNWINRT_TEST_PYTHON") + .map(PathBuf::from) + .unwrap_or_else(|| PathBuf::from("python")); + let checker = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .join("tests") + .join("e2e") + .join("check_generated_python.py"); + let checked = Command::new(&python) + .arg(checker) + .arg(&generated) + .output() + .unwrap(); + assert!( + checked.status.success(), + "{}\n{}", + String::from_utf8_lossy(&checked.stdout), + String::from_utf8_lossy(&checked.stderr) + ); + let mypy_available = Command::new(&python) + .args(["-m", "mypy", "--version"]) + .output() + .is_ok_and(|output| output.status.success()); + assert!( + mypy_available || std::env::var("DYNWINRT_REQUIRE_MYPY").as_deref() != Ok("1"), + "DYNWINRT_REQUIRE_MYPY=1 but mypy is unavailable" + ); + for (file, consumer, expected_errors) in [ + ( + "valid.py", + r#"from typing import assert_type +from dynwinrt import DynWinRTValue, DynWinRtDelegate +from projected.audit import IEmitter + +def use(emitter: IEmitter, raw: DynWinRTValue, native: DynWinRtDelegate) -> None: + def callback(inner: DynWinRTValue | None) -> None: + if inner is not None: + inner.identity_raw() + emitter.set_handler(callback) + emitter.set_handler(lambda inner: assert_type(inner, DynWinRTValue | None)) + emitter.set_handler(raw) + emitter.set_handler(native) +"#, + 0, + ), + ( + "invalid.py", + r#"from dynwinrt import DynWinRTValue +from projected.audit import IEmitter + +def nonnullable(inner: DynWinRTValue) -> None: ... +def invalid(emitter: IEmitter) -> None: + emitter.set_handler(nonnullable) +"#, + 1, + ), + ] { + fs::write( + fixture.0.join(file), + format!("# pyright: strict, reportPrivateUsage=false\n{consumer}"), + ) + .unwrap(); + if mypy_available { + let output = Command::new(&python) + .args([ + "-B", + "-m", + "mypy", + "--strict", + "--no-incremental", + "--follow-imports=silent", + "--no-pretty", + "--show-error-codes", + "--cache-dir", + ".mypy_cache", + ]) + .arg(file) + .current_dir(&fixture.0) + .env( + "MYPYPATH", + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join("bindings") + .join("py"), + ) + .output() + .unwrap(); + let diagnostics = format!( + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let errors = diagnostics + .lines() + .filter(|line| line.contains(": error:")) + .count(); + assert_eq!(errors, expected_errors, "{diagnostics}"); + assert_eq!( + output.status.success(), + expected_errors == 0, + "{diagnostics}" + ); + if expected_errors != 0 { + assert!(diagnostics.contains("[arg-type]"), "{diagnostics}"); + } + } + if let Some(pyright) = std::env::var_os("DYNWINRT_PYRIGHT") { + let output = Command::new(pyright) + .args(["--pythonpath"]) + .arg(&python) + .arg(file) + .current_dir(&fixture.0) + .output() + .unwrap(); + let diagnostics = format!( + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + let errors = diagnostics + .lines() + .filter(|line| line.contains(" - error: ")) + .count(); + assert_eq!(errors, expected_errors, "{diagnostics}"); + assert_eq!( + output.status.success(), + expected_errors == 0, + "{diagnostics}" + ); + if expected_errors != 0 { + assert!(diagnostics.contains("reportArgumentType"), "{diagnostics}"); + } + } + } + + let runtime_available = Command::new(&python) + .args(["-c", "from dynwinrt import DynWinRTImplementation"]) + .output() + .is_ok_and(|output| output.status.success()); + assert!( + runtime_available + || std::env::var("DYNWINRT_REQUIRE_IMPLEMENTATION_RUNTIME").as_deref() != Ok("1"), + "the nested delegate callback probe requires the matching Python binding" + ); + if !runtime_available { + eprintln!("Skipping nested delegate native callback: set DYNWINRT_TEST_PYTHON."); + return; + } + fs::write( + fixture.0.join("native.py"), + r#"from dynwinrt import ( + DynWinRTImplementation, DynWinRTImplementationMethod, DynWinRTInterfacePlan, + DynWinRTMethodSig, DynWinRTType, DynWinRTValue, DynWinRtDelegate, RoApartment, + WinGUID, projected_lifetime_scope, release_projected, +) +from projected.audit import IEmitter + +inner_iid = WinGUID.parse("31d447a1-6281-4900-b782-040302010910") +outer_iid = WinGUID.parse("31d447a2-6281-4900-b782-040302010910") +emitter_iid = WinGUID.parse("31d447a3-6281-4900-b782-040302010910") +emitter_sig = DynWinRTMethodSig().add_in(DynWinRTType.delegate(outer_iid)) +emitter_type = DynWinRTType.register_interface("Audit.IEmitter", emitter_iid) +emitter_type = emitter_type.add_method("SetHandler", emitter_sig) +plan = DynWinRTInterfacePlan.create( + "Audit.IEmitter", emitter_type, + [DynWinRTImplementationMethod("SetHandler", 6, emitter_sig)], +) +outer_sig = DynWinRTMethodSig().add_in(DynWinRTType.delegate(inner_iid)) +received = [] + +with RoApartment(1), projected_lifetime_scope(): + inner = DynWinRtDelegate.create(inner_iid, [], lambda: None) + inner_value = inner.to_value() + def callback(argument): + if argument is None: + received.append(None) + else: + assert isinstance(argument, DynWinRTValue), type(argument) + assert not argument.is_null() + received.append(argument.identity_raw() == inner_value.identity_raw()) + def dispatch(_interface, slot, args): + assert slot == 6 + args[0].invoke_delegate(outer_iid, outer_sig, [DynWinRTValue.null_value()]) + args[0].invoke_delegate(outer_iid, outer_sig, [inner_value]) + return [] + try: + with DynWinRTImplementation.create([plan], dispatch) as owner: + value = owner.to_value() + try: + emitter = IEmitter.from_value(value) + try: + emitter.set_handler(callback) + finally: + release_projected(emitter) + finally: + value.release() + finally: + inner_value.release() +assert received == [None, True], received +print("delegate-typed-native-null-ok", received) +"#, + ) + .unwrap(); + let output = Command::new(&python) + .args(["-B", "native.py"]) + .current_dir(&fixture.0) + .output() + .unwrap(); + let diagnostics = format!( + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + assert!(output.status.success(), "{diagnostics}"); + assert!( + diagnostics.contains("delegate-typed-native-null-ok [None, True]"), + "{diagnostics}" + ); + eprintln!("{}", String::from_utf8_lossy(&output.stdout).trim()); +} + #[test] fn bespoke_event_delegates_are_typed_and_projected() { let Some(output) = diff --git a/tools/dynwinrt-codegen/tests/python_inheritance_typing_test.rs b/tools/dynwinrt-codegen/tests/python_inheritance_typing_test.rs index bdf317d0..2bd3505d 100644 --- a/tools/dynwinrt-codegen/tests/python_inheritance_typing_test.rs +++ b/tools/dynwinrt-codegen/tests/python_inheritance_typing_test.rs @@ -135,7 +135,7 @@ fn stubs_model_runtime_class_and_interface_bases_without_runtime_inheritance() { "{class_stub}" ); assert!( - class_stub.contains("def use_base(self, value: 'BaseLike') -> Base | None:"), + class_stub.contains("def use_base(self, value: 'BaseLike') -> Base:"), "{class_stub}" ); assert!( diff --git a/tools/dynwinrt-codegen/tests/python_overload_normalization_test.rs b/tools/dynwinrt-codegen/tests/python_overload_normalization_test.rs index 59a96f76..92fcff80 100644 --- a/tools/dynwinrt-codegen/tests/python_overload_normalization_test.rs +++ b/tools/dynwinrt-codegen/tests/python_overload_normalization_test.rs @@ -843,11 +843,11 @@ def formatting(formatter: DecimalFormatter) -> None: assert_type(formatter.format_u_int(5), str) async def storage(file: StorageFile, folder: StorageFolder) -> None: - assert_type(await file.copy_async(folder), StorageFile | None) - assert_type(await file.copy_async(folder, "name.txt"), StorageFile | None) + assert_type(await file.copy_async(folder), StorageFile) + assert_type(await file.copy_async(folder, "name.txt"), StorageFile) option = NameCollisionOption.ReplaceExisting - assert_type(await file.copy_async(folder, "name.txt", option), StorageFile | None) - assert_type(await file.copy_overload(folder, "name.txt", option), StorageFile | None) + assert_type(await file.copy_async(folder, "name.txt", option), StorageFile) + assert_type(await file.copy_overload(folder, "name.txt", option), StorageFile) await file.move_async(folder) async def launching(file: StorageFile, uri: Uri) -> None: diff --git a/tools/dynwinrt-codegen/tests/python_stub_nullability_test.rs b/tools/dynwinrt-codegen/tests/python_stub_nullability_test.rs new file mode 100644 index 00000000..e095a9e8 --- /dev/null +++ b/tools/dynwinrt-codegen/tests/python_stub_nullability_test.rs @@ -0,0 +1,491 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! The stub nullability policy on real Windows SDK metadata. Values received +//! from the projection are non-null in `.pyi` stubs by default, while +//! `IReference`, `Try*` results, members documented to return null, +//! `Object` and delegate values keep `| None`. Collection elements follow the +//! native nullable-reference contract. Inputs, implementation protocols and +//! the runtime `.py` annotations are unchanged. + +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use dynwinrt_codegen::codegen::{python, python_stub}; +use dynwinrt_codegen::meta::{InterfaceMeta, MethodMeta}; +use dynwinrt_codegen::types::TypeMeta; + +const WINDOWS_WINMD: &str = + r"C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.26100.0\Windows.winmd"; + +struct Generated(PathBuf); + +impl Drop for Generated { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } +} + +impl Generated { + fn new(label: &str, classes: &str) -> Option { + if !Path::new(WINDOWS_WINMD).is_file() { + eprintln!("Skipping: Windows.winmd not found"); + return None; + } + let root = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .join("target") + .join(format!("pn{label}{}", std::process::id())); + let _ = fs::remove_dir_all(&root); + let output = Command::new(env!("CARGO_BIN_EXE_dynwinrt-codegen")) + .args([ + "generate", + "--winmd", + WINDOWS_WINMD, + "--class-name", + classes, + ]) + .args(["--lang", "py", "--output"]) + .arg(&root) + .output() + .unwrap(); + assert!( + output.status.success(), + "{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + Some(Self(root)) + } + + fn module(&self, name: &str) -> String { + fs::read_to_string(self.0.join(name)).unwrap_or_else(|error| panic!("{name}: {error}")) + } +} + +fn assert_contains(text: &str, expected: &str) { + assert!(text.contains(expected), "missing `{expected}`"); +} + +#[test] +fn ordinary_reference_array_stub_elements_are_nullable() { + let widget = TypeMeta::RuntimeClass { + namespace: "Contoso".into(), + name: "Widget".into(), + default_interface: None, + }; + let widget_interface = TypeMeta::Interface { + namespace: "Contoso".into(), + name: "IWidget".into(), + iid: "11111111-1111-1111-1111-111111111111".into(), + }; + let handler = TypeMeta::Delegate { + namespace: "Contoso".into(), + name: "WidgetHandler".into(), + iid: "22222222-2222-2222-2222-222222222222".into(), + }; + let reference = TypeMeta::Parameterized { + namespace: "Windows.Foundation".into(), + name: "IReference`1".into(), + piid: "61c17706-2d65-11e0-9ae8-d48564015472".into(), + args: vec![TypeMeta::U32], + }; + let view = TypeMeta::Parameterized { + namespace: "Windows.Foundation.Collections".into(), + name: "IVectorView`1".into(), + piid: "bbe1fa4c-b0e3-4583-baef-1f1b2e483e56".into(), + args: vec![widget.clone()], + }; + let method = |name: &str, typ: TypeMeta, slot: usize| MethodMeta { + name: name.into(), + raw_name: name.into(), + vtable_index: slot, + return_type: Some(typ), + ..Default::default() + }; + let source = InterfaceMeta { + namespace: "Contoso".into(), + name: "IArraySource".into(), + iid: "33333333-3333-3333-3333-333333333333".into(), + methods: vec![ + method("GetWidgets", TypeMeta::Array(Box::new(widget.clone())), 6), + method( + "GetInterfaces", + TypeMeta::Array(Box::new(widget_interface.clone())), + 7, + ), + method("GetObjects", TypeMeta::Array(Box::new(TypeMeta::Object)), 8), + method( + "GetDelegates", + TypeMeta::Array(Box::new(handler.clone())), + 9, + ), + method( + "GetReferences", + TypeMeta::Array(Box::new(reference.clone())), + 10, + ), + method("GetViews", TypeMeta::Array(Box::new(view.clone())), 11), + method( + "GetWidgetsAsync", + TypeMeta::AsyncOperation(Box::new(TypeMeta::Array(Box::new(widget.clone())))), + 12, + ), + method( + "GetStrings", + TypeMeta::Array(Box::new(TypeMeta::String)), + 13, + ), + method("GetGuids", TypeMeta::Array(Box::new(TypeMeta::Guid)), 14), + method("GetNumbers", TypeMeta::Array(Box::new(TypeMeta::I32)), 15), + method("GetBytes", TypeMeta::Array(Box::new(TypeMeta::U8)), 16), + ], + ..Default::default() + }; + let context = python::PythonProjectionContext::standalone([ + source.type_identity(), + widget.type_identity(), + widget_interface.type_identity(), + handler.type_identity(), + reference.type_identity(), + view.type_identity(), + ]) + .unwrap(); + let stub = python_stub::generate_interface_stub(&context, &source); + + for expected in [ + "def get_widgets(self) -> list[Widget | None]: ...", + "def get_interfaces(self) -> list[IWidget | None]: ...", + "def get_objects(self) -> list[DynWinRTValue | None]: ...", + "def get_delegates(self) -> list[DynWinRTValue | None]: ...", + "def get_references(self) -> list[IReference_UInt32 | None]: ...", + "def get_views(self) -> list[IVectorView_Widget | None]: ...", + "def get_widgets_async(self) -> WinRTCoroutine[list[Widget | None]]: ...", + "def get_strings(self) -> list[str]: ...", + "def get_guids(self) -> list[UUID]: ...", + "def get_numbers(self) -> list[int]: ...", + "def get_bytes(self) -> bytes: ...", + ] { + assert_contains(&stub, expected); + } + assert!( + !stub.contains("list[Widget | None] | None"), + "array values must remain non-null" + ); +} + +#[test] +fn stub_outputs_follow_the_nullability_policy() { + let Some(generated) = Generated::new( + "out", + "Windows.Storage.StorageFolder,Windows.Web.Http.Headers.HttpContentHeaderCollection,\ + Windows.Foundation.Collections.PropertySet,Windows.Data.Json.JsonObject,\ + Windows.Devices.Sensors.Accelerometer,Windows.Devices.Sensors.Compass,\ + Windows.System.DispatcherQueue,Windows.Data.Xml.Dom.XmlDocument", + ) else { + return; + }; + let folder = generated.module("windows__storage__storage_folder.pyi"); + let folder_py = generated.module("windows__storage__storage_folder.py"); + let folder_view = generated.module("windows__storage__i_storage_folder.pyi"); + let headers = + generated.module("windows__web__http__headers__http_content_header_collection.pyi"); + let properties = generated.module("windows__foundation__collections__property_set.pyi"); + let json = generated.module("windows__data__json__json_object.pyi"); + let accelerometer = generated.module("windows__devices__sensors__accelerometer.pyi"); + let compass = generated.module("windows__devices__sensors__compass.pyi"); + let dispatcher = generated.module("windows__system__dispatcher_queue.pyi"); + let xml = generated.module("windows__data__xml__dom__xml_document.pyi"); + + // Method, async, collection and property outputs are non-null by default. + assert_contains( + &folder, + "def create_file_async(self, desired_name: str) -> WinRTCoroutine[StorageFile]: ...", + ); + assert_contains( + &folder, + "def get_files_async(self) -> WinRTCoroutine[Sequence[StorageFile | None]]: ...", + ); + assert_contains( + &folder, + "def get_folder_from_path_async(path: str) -> WinRTCoroutine[StorageFolder]: ...", + ); + assert_contains( + &folder, + "def properties(self) -> StorageItemContentProperties: ...", + ); + assert_contains( + &json, + "def get_named_object(self, name: str) -> JsonObject: ...", + ); + assert_contains(&json, "def parse(input: str) -> JsonObject: ..."); + + // Try* results keep None on their result, async result and out values. + assert_contains( + &folder, + "def try_get_item_async(self, name: str) -> WinRTCoroutine[IStorageItem | None]: ...", + ); + assert_contains( + &json, + "def try_parse(input: str) -> tuple[JsonObject | None, bool]: ...", + ); + + // Members the Windows SDK documentation says can return null keep None, + // including through overloads, interfaces and async results. + assert_contains( + &accelerometer, + "def get_default() -> Accelerometer | None: ...", + ); + assert_contains( + &accelerometer, + "def get_default_with_accelerometer_reading_type(reading_type: 'AccelerometerReadingType') -> Accelerometer | None: ...", + ); + assert_contains(&compass, "def get_default() -> Compass | None: ..."); + assert_contains( + &dispatcher, + "def get_for_current_thread() -> DispatcherQueue | None: ...", + ); + assert_contains( + &dispatcher, + "def create_timer(self) -> DispatcherQueueTimer: ...", + ); + assert_contains( + &folder, + "def get_parent_async(self) -> WinRTCoroutine[StorageFolder | None]: ...", + ); + assert_contains( + &xml, + "def select_single_node(self, xpath: str) -> IXmlNode | None: ...", + ); + + // IReference values and Object values keep None, and so do properties + // whose documentation says a null value means "absent". + assert_contains(&headers, "def content_length(self) -> int | None: ..."); + assert_contains( + &headers, + "def content_type(self) -> HttpMediaTypeHeaderValue | None: ...", + ); + assert_contains( + &properties, + "def lookup(self, key: str) -> DynWinRTValue | None: ...", + ); + assert_contains( + &properties, + "def __getitem__(self, key: str) -> DynWinRTValue | None: ...", + ); + + // Inputs are unchanged. + assert_contains( + &headers, + "def content_length(self, value: int | None | IReference_UInt64) -> None: ...", + ); + assert_contains( + &folder, + "def create_folder_query(self, query_options: 'QueryOptionsLike') -> StorageFolderQueryResult: ...", + ); + assert_contains( + &properties, + "def __setitem__(self, key: str, value: DynWinRTValue | _DynWinRTObject | None) -> None: ...", + ); + + // Implementation protocols keep their obligations. + assert_contains(&folder_view, "class IStorageFolderHandlers(Protocol):"); + assert_contains( + &folder_view, + "def create_file_async_overload_default_options(self, desired_name: str) -> DynWinRTValue | None: ...", + ); + + // Runtime annotations stay pessimistic. + assert_contains( + &folder_py, + "def get_folder_from_path_async(path: str) -> WinRTCoroutine[StorageFolder | None]:", + ); + assert_contains( + &folder_py, + "def properties(self) -> StorageItemContentProperties | None:", + ); +} + +#[test] +fn sensor_current_reading_docs_control_nullability_without_family_false_positives() { + let Some(generated) = Generated::new( + "sensorreadings", + "Windows.Devices.Sensors.Accelerometer,Windows.Devices.Sensors.Compass,\ + Windows.Devices.Sensors.Gyrometer,Windows.Devices.Sensors.Inclinometer,\ + Windows.Devices.Sensors.LightSensor,Windows.Devices.Sensors.OrientationSensor,\ + Windows.Devices.Sensors.Custom.CustomSensor,Windows.Devices.Sensors.Altimeter,\ + Windows.Devices.Sensors.Barometer,Windows.Devices.Sensors.HumanPresenceSensor,\ + Windows.Devices.Sensors.Magnetometer,Windows.Devices.Sensors.ProximitySensor,\ + Windows.Devices.Sensors.ActivitySensor,Windows.Devices.Sensors.HingeAngleSensor,\ + Windows.Devices.Sensors.Pedometer", + ) else { + return; + }; + + for (module, signature) in [ + ( + "windows__devices__sensors__accelerometer.pyi", + "def get_current_reading(self) -> AccelerometerReading | None: ...", + ), + ( + "windows__devices__sensors__compass.pyi", + "def get_current_reading(self) -> CompassReading | None: ...", + ), + ( + "windows__devices__sensors__custom__custom_sensor.pyi", + "def get_current_reading(self) -> CustomSensorReading | None: ...", + ), + ( + "windows__devices__sensors__gyrometer.pyi", + "def get_current_reading(self) -> GyrometerReading | None: ...", + ), + ( + "windows__devices__sensors__inclinometer.pyi", + "def get_current_reading(self) -> InclinometerReading | None: ...", + ), + ( + "windows__devices__sensors__light_sensor.pyi", + "def get_current_reading(self) -> LightSensorReading | None: ...", + ), + ( + "windows__devices__sensors__orientation_sensor.pyi", + "def get_current_reading(self) -> OrientationSensorReading | None: ...", + ), + ] { + assert_contains(&generated.module(module), signature); + } + + // The other GetCurrentReading variants at the pinned docs revision do + // not contain the required-null-check wording and remain non-null. + for (module, signature) in [ + ( + "windows__devices__sensors__activity_sensor.pyi", + "def get_current_reading_async(self) -> WinRTCoroutine[ActivitySensorReading]: ...", + ), + ( + "windows__devices__sensors__altimeter.pyi", + "def get_current_reading(self) -> AltimeterReading: ...", + ), + ( + "windows__devices__sensors__barometer.pyi", + "def get_current_reading(self) -> BarometerReading: ...", + ), + ( + "windows__devices__sensors__hinge_angle_sensor.pyi", + "def get_current_reading_async(self) -> WinRTCoroutine[HingeAngleReading]: ...", + ), + ( + "windows__devices__sensors__human_presence_sensor.pyi", + "def get_current_reading(self) -> HumanPresenceSensorReading: ...", + ), + ( + "windows__devices__sensors__magnetometer.pyi", + "def get_current_reading(self) -> MagnetometerReading: ...", + ), + ( + "windows__devices__sensors__proximity_sensor.pyi", + "def get_current_reading(self) -> ProximitySensorReading: ...", + ), + ] { + assert_contains(&generated.module(module), signature); + } + assert_contains( + &generated.module("windows__devices__sensors__pedometer.pyi"), + "def get_current_readings(self) -> Mapping['PedometerStepKind', PedometerReading | None]: ...", + ); +} + +#[test] +fn reference_collection_elements_are_nullable_regardless_of_provenance() { + let Some(generated) = Generated::new( + "elements", + "Windows.Storage.StorageFolder,Windows.Data.Json.JsonObject,\ + Windows.ApplicationModel.Resources.Core.ResourceMap,Windows.Media.Playback.MediaPlaybackList,\ + Windows.Foundation.Collections.StringMap", + ) else { + return; + }; + let files = + generated.module("windows__foundation__collections__i_vector_view_storage_file.pyi"); + let array = generated.module("windows__data__json__json_array.pyi"); + let object = generated.module("windows__data__json__json_object.pyi"); + let resources = + generated.module("windows__application_model__resources__core__resource_map.pyi"); + let playlist = generated.module("windows__media__playback__media_playback_list.pyi"); + let observable = generated + .module("windows__foundation__collections__i_observable_vector_media_playback_item.pyi"); + let string_map_view = + generated.module("windows__foundation__collections__i_map_view_string_string.pyi"); + let string_map_view_py = + generated.module("windows__foundation__collections__i_map_view_string_string.py"); + + // Collection interfaces carry no provenance. A view or iterator obtained + // from a mutable collection can expose a null slot, so view item + // positions conservatively keep None too. + assert_contains( + &files, + "def get_at(self, index: int) -> StorageFile | None: ...", + ); + assert_contains( + &files, + "def __getitem__(self, index: int) -> StorageFile | None: ...", + ); + assert_contains( + &resources, + "class ResourceMap(_ResourceMapIdentity, Mapping[str, NamedResource | None], _DynWinRTRuntimeClass):", + ); + assert_contains( + &resources, + "def lookup(self, key: str) -> NamedResource | None: ...", + ); + + // Mutable collection elements, item positions and element-reading + // members keep None for the same reason. + assert_contains( + &array, + "class JsonArray(_JsonArrayIdentity, MutableSequence[IJsonValue | None], _DynWinRTRuntimeClass):", + ); + assert_contains( + &array, + "def get_at(self, index: int) -> IJsonValue | None: ...", + ); + assert_contains( + &array, + "def __getitem__(self, index: int) -> IJsonValue | None: ...", + ); + assert_contains( + &array, + "def get_object_at(self, index: int) -> JsonObject: ...", + ); + assert_contains( + &object, + "class JsonObject(_JsonObjectIdentity, MutableMapping[str, IJsonValue | None], _DynWinRTRuntimeClass):", + ); + assert_contains( + &object, + "def lookup(self, key: str) -> IJsonValue | None: ...", + ); + assert_contains( + &object, + "def get_named_array(self, name: str) -> JsonArray: ...", + ); + assert_contains( + &playlist, + "def items(self) -> MutableSequence[MediaPlaybackItem | None]: ...", + ); + assert_contains(&observable, "MutableSequence[MediaPlaybackItem | None]):"); + assert_contains( + &observable, + "def __getitem__(self, index: int) -> MediaPlaybackItem | None: ...", + ); + // Split succeeds with two optional view pointers; no other IMapView + // result is widened by this native contract. + let split = "def split(self) -> tuple[Mapping[str, str] | None, Mapping[str, str] | None]: ..."; + assert_contains(&string_map_view, split); + assert_contains(&string_map_view, "def lookup(self, key: str) -> str: ..."); + assert_contains(&string_map_view_py, split.trim_end_matches(" ...")); +} diff --git a/tools/dynwinrt-codegen/tests/python_symbol_mapping_test.rs b/tools/dynwinrt-codegen/tests/python_symbol_mapping_test.rs index 739bda9b..01fbc7d6 100644 --- a/tools/dynwinrt-codegen/tests/python_symbol_mapping_test.rs +++ b/tools/dynwinrt-codegen/tests/python_symbol_mapping_test.rs @@ -331,7 +331,7 @@ fn python_class_self_and_base_markers_use_declarations_in_both_layouts() { "fixture must require a local self binding" ); assert!( - stub.contains("def echo_self(self, value: 'WidgetLike') -> Widget | None:"), + stub.contains("def echo_self(self, value: 'WidgetLike') -> Widget:"), "{stub}" ); assert!( @@ -375,13 +375,13 @@ fn python_class_self_and_base_markers_use_declarations_in_both_layouts() { def alpha(value: AlphaWidget, peer: BetaWidget, child: AlphaDerived) -> None: own: AlphaBaseLike = value foreign: BetaBaseLike = child - assert_type(value.echo_self(value), AlphaWidget | None) - assert_type(value.echo_foreign(peer), BetaWidget | None) + assert_type(value.echo_self(value), AlphaWidget) + assert_type(value.echo_foreign(peer), BetaWidget) def beta(value: BetaWidget, peer: AlphaWidget, child: BetaDerived) -> None: own: BetaBaseLike = value foreign: AlphaBaseLike = child - assert_type(value.echo_self(value), BetaWidget | None) - assert_type(value.echo_foreign(peer), AlphaWidget | None) + assert_type(value.echo_self(value), BetaWidget) + assert_type(value.echo_foreign(peer), AlphaWidget) "#, ); support(&package, &[]); @@ -587,13 +587,13 @@ fn python_named_class_closed_generic_collision_uses_allocated_declaration() { from typing import assert_type assert_type(Named(), Named) assert_type(Named(7), Named) -assert_type(Named.get_current(), Named | None) +assert_type(Named.get_current(), Named) def check(named: Named, generic: Bucket, derived: Derived) -> None: base: NamedLike = derived - assert_type(named.echo_self(named), Named | None) - assert_type(named.echo_generic(generic), Bucket | None) - assert_type(generic.echo_self(generic), Bucket | None) - assert_type(generic.echo_named(named), Named | None) + assert_type(named.echo_self(named), Named) + assert_type(named.echo_generic(generic), Bucket) + assert_type(generic.echo_self(generic), Bucket) + assert_type(generic.echo_named(named), Named) "#, ), ); @@ -2172,8 +2172,8 @@ fn python_cross_role_class_owners_keep_self_bindings_and_qualified_helpers() { r#"{imports} from typing import assert_type def check(value: Owner) -> None: - assert_type(value.echo_self(value), Owner | None) - assert_type(value.get_self(), Owner | None) + assert_type(value.echo_self(value), Owner) + assert_type(value.get_self(), Owner) assert_type(value.echo_payload(URLValue(17)), URLValue) "# ), @@ -2705,9 +2705,9 @@ fn python_enum_closed_generic_collision_preserves_projection_and_native_identity from typing import assert_type assert_type(RootKind(0), Kind) def check(local: IUse, foreign: IForeign) -> None: - assert_type(local.get_bucket(), Bucket | None) - assert_type(local.get_bucket(), RootBucket | None) - assert_type(foreign.get_bucket(), Bucket | None) + assert_type(local.get_bucket(), Bucket) + assert_type(local.get_bucket(), RootBucket) + assert_type(foreign.get_bucket(), Bucket) assert_type(local.echo_kind(Kind.Unknown), Kind) assert_type(foreign.echo_kind(Kind.Unknown), Kind) assert_type(local.echo_kinds([Kind.Unknown]), list[Kind]) @@ -2911,10 +2911,10 @@ fn python_class_companion_aliases_preserve_cli_and_standalone_contracts() { r#"{imports} from typing import assert_type def check(owner: Widget, peer: Peer, use: IUse) -> None: - assert_type(owner.echo(peer), Peer | None) - assert_type(peer.echo(owner), Widget | None) - assert_type(use.echo_owner(owner), Widget | None) - assert_type(use.echo_peer(peer), Peer | None) + assert_type(owner.echo(peer), Peer) + assert_type(peer.echo(owner), Widget) + assert_type(use.echo_owner(owner), Widget) + assert_type(use.echo_peer(peer), Peer) "#, ), ); 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 484ad085..f6494623 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 @@ -14,6 +14,7 @@ _dynwinrt_symbol, _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, _dynwinrt_wrap_values, ) +from ._runtime import _dynwinrt_collection_item from dynwinrt.dynwinrt import _WinRTIteratorMixin if TYPE_CHECKING: 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 c1c7cd1b..15657987 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 @@ -14,6 +14,7 @@ _dynwinrt_symbol, _dynwinrt_track_projected, _dynwinrt_uuid, _dynwinrt_vector, _dynwinrt_wrap_values, ) +from ._runtime import _dynwinrt_collection_item from dynwinrt.dynwinrt import _WinRTIterableMixin, _WinRTSequenceMixin if TYPE_CHECKING: @@ -99,8 +100,8 @@ def size(self) -> int: def get_at(self, index: int) -> IWwwFormUrlDecoderEntry | None: 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)]) + def index_of(self, value: IWwwFormUrlDecoderEntry | None) -> tuple[int, bool]: + _results = _IVectorView_IWwwFormUrlDecoderEntry.method(8).invoke_all(self._collection_obj, [_dynwinrt_collection_item(value, lambda item: getattr(item, '_obj', item), True, 'collection element')]) return (_results[0].to_u32(), _results[1].to_bool()) def get_many(self, start_index: int, items: DynWinRTArray | Sequence['IWwwFormUrlDecoderEntry']) -> list[IWwwFormUrlDecoderEntry | None]: @@ -151,8 +152,8 @@ def size(self) -> int: def get_at(self, index: int) -> IWwwFormUrlDecoderEntry | None: 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)]) + def index_of(self, value: IWwwFormUrlDecoderEntry | None) -> tuple[int, bool]: + _results = _IVectorView_IWwwFormUrlDecoderEntry.method(8).invoke_all(self._obj, [_dynwinrt_collection_item(value, lambda item: getattr(item, '_obj', item), True, 'collection element')]) return (_results[0].to_u32(), _results[1].to_bool()) def get_many(self, start_index: int, items: DynWinRTArray | Sequence['IWwwFormUrlDecoderEntry']) -> list[IWwwFormUrlDecoderEntry | None]: diff --git a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi index 815ffece..728dceb6 100644 --- a/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi +++ b/tools/dynwinrt-codegen/tests/snapshots/uri_pyi/uri.pyi @@ -60,7 +60,7 @@ class UriLike(_UriIdentity, Protocol): def query(self) -> str: ... @builtins.property - def query_parsed(self) -> WwwFormUrlDecoder | None: ... + def query_parsed(self) -> WwwFormUrlDecoder: ... @builtins.property def raw_uri(self) -> str: ... @@ -79,7 +79,7 @@ class UriLike(_UriIdentity, Protocol): def equals(self, p_uri: 'UriLike') -> bool: ... - def combine_uri(self, relative_uri: str) -> Uri | None: ... + def combine_uri(self, relative_uri: str) -> Uri: ... @builtins.property def absolute_canonical_uri(self) -> str: ... 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 46688389..cab1e75a 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 @@ -45,11 +45,11 @@ class WwwFormUrlDecoderLike(_WwwFormUrlDecoderIdentity, Protocol): def get_at(self, index: int) -> IWwwFormUrlDecoderEntry | None: ... - def index_of(self, value: 'IWwwFormUrlDecoderEntry') -> tuple[int, bool]: ... + def index_of(self, value: IWwwFormUrlDecoderEntry | None) -> tuple[int, bool]: ... def get_many(self, start_index: int, items: DynWinRTArray | Sequence['IWwwFormUrlDecoderEntry']) -> list[IWwwFormUrlDecoderEntry | None]: ... - def first(self) -> Iterator[IWwwFormUrlDecoderEntry | None] | None: ... + def first(self) -> Iterator[IWwwFormUrlDecoderEntry | None]: ... def as_interface(self, interface_class: _DynWinRTProjector[_InterfaceT]) -> _InterfaceT: ... @@ -71,11 +71,11 @@ class WwwFormUrlDecoder(_WwwFormUrlDecoderIdentity, Sequence[IWwwFormUrlDecoderE def get_at(self, index: int) -> IWwwFormUrlDecoderEntry | None: ... - def index_of(self, value: 'IWwwFormUrlDecoderEntry') -> tuple[int, bool]: ... + def index_of(self, value: IWwwFormUrlDecoderEntry | None) -> tuple[int, bool]: ... def get_many(self, start_index: int, items: DynWinRTArray | Sequence['IWwwFormUrlDecoderEntry']) -> list[IWwwFormUrlDecoderEntry | None]: ... - def first(self) -> Iterator[IWwwFormUrlDecoderEntry | None] | None: ... + def first(self) -> Iterator[IWwwFormUrlDecoderEntry | None]: ... def as_interface(self, interface_class: _DynWinRTProjector[_InterfaceT]) -> _InterfaceT: ... @@ -103,7 +103,7 @@ class IVectorView_IWwwFormUrlDecoderEntry(Sequence[IWwwFormUrlDecoderEntry | Non def get_at(self, index: int) -> IWwwFormUrlDecoderEntry | None: ... - def index_of(self, value: 'IWwwFormUrlDecoderEntry') -> tuple[int, bool]: ... + def index_of(self, value: IWwwFormUrlDecoderEntry | None) -> tuple[int, bool]: ... def get_many(self, start_index: int, items: DynWinRTArray | Sequence['IWwwFormUrlDecoderEntry']) -> list[IWwwFormUrlDecoderEntry | None]: ... @@ -119,4 +119,4 @@ class IIterable_IWwwFormUrlDecoderEntry(Iterable[IWwwFormUrlDecoderEntry | None] def from_value(cls, obj: DynWinRTValue) -> Self: ... def as_interface(self, interface_class: _DynWinRTProjector[_InterfaceT]) -> _InterfaceT: ... - def first(self) -> Iterator[IWwwFormUrlDecoderEntry | None] | None: ... + def first(self) -> Iterator[IWwwFormUrlDecoderEntry | None]: ...