From 94468e7cb495355a050398d5412c67dcb823851d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Dec 2025 12:02:45 -0800 Subject: [PATCH 1/5] Add remarks to Keys and Values properties in dictionary classes Added XML documentation remarks to the Keys and Values properties in WindowsRuntimeDictionary, WindowsRuntimeObservableMap, and WindowsRuntimeReadOnlyDictionary classes to clarify the concrete collection types returned. This improves API documentation and developer understanding of the property return types. --- .../Collections/WindowsRuntimeDictionary{TKey, TValue}.cs | 8 ++++++++ .../WindowsRuntimeObservableMap{TKey, TValue}.cs | 8 ++++++++ .../WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs | 6 ++++++ 3 files changed, 22 insertions(+) diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs index 1f6a55a3b..93cd2f752 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs @@ -90,15 +90,23 @@ WindowsRuntimeObjectReference InitializeIIterableObjectReference() protected internal sealed override bool HasUnwrappableNativeObjectReference => true; /// + /// + /// The resulting object will be of type . + /// public ICollection Keys => _keys ??= new DictionaryKeyCollection(this); /// + /// IEnumerable IReadOnlyDictionary.Keys => Keys; /// + /// + /// The resulting object will be of type . + /// public ICollection Values => _values ??= new DictionaryValueCollection(this); /// + /// IEnumerable IReadOnlyDictionary.Values => Values; /// diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs index efa2a4a95..8842509ea 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs @@ -124,15 +124,23 @@ public event MapChangedEventHandler? MapChanged protected internal sealed override bool HasUnwrappableNativeObjectReference => true; /// + /// + /// The resulting object will be of type . + /// public ICollection Keys => _keys ??= new DictionaryKeyCollection(this); /// + /// IEnumerable IReadOnlyDictionary.Keys => Keys; /// + /// + /// The resulting object will be of type . + /// public ICollection Values => _values ??= new DictionaryValueCollection(this); /// + /// IEnumerable IReadOnlyDictionary.Values => Values; /// diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs index df74e42c4..aab1e0e28 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs @@ -89,9 +89,15 @@ WindowsRuntimeObjectReference InitializeIIterableObjectReference() protected internal sealed override bool HasUnwrappableNativeObjectReference => true; /// + /// + /// The resulting object will be of type . + /// public IEnumerable Keys => _keys ??= new ReadOnlyDictionaryKeyCollection(this); /// + /// + /// The resulting object will be of type . + /// public IEnumerable Values => _values ??= new ReadOnlyDictionaryValueCollection(this); /// From 92ae2e6a95bde762937141e861aae7b22f794ce1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Dec 2025 13:03:37 -0800 Subject: [PATCH 2/5] Refactor generic interface tracking logic Replaces direct calls to TrackGenericInterfaceType with a new TryTrackWindowsRuntimeGenericInterfaceTypeInstance method. This centralizes and extends the logic for tracking constructed generic Windows Runtime interface types, ensuring all necessary related types and delegates are also tracked for code generation. --- .../InteropTypeDiscovery.Generics.cs | 102 +++++++++++++++++- .../Discovery/InteropTypeDiscovery.cs | 2 +- 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs index 593d5640a..a31e29814 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs @@ -165,7 +165,7 @@ private static void TryTrackWindowsRuntimeGenericTypeInstance( // Track all projected Windows Runtime generic interfaces if (typeDefinition.IsInterface) { - discoveryState.TrackGenericInterfaceType(typeSignature, interopReferences); + TryTrackWindowsRuntimeGenericInterfaceTypeInstance(typeSignature, discoveryState, interopReferences); // We also want to crawl base interfaces foreach (TypeSignature interfaceSignature in typeSignature.EnumerateAllInterfaces()) @@ -191,11 +191,109 @@ private static void TryTrackWindowsRuntimeGenericTypeInstance( continue; } - discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences); + TryTrackWindowsRuntimeGenericInterfaceTypeInstance(constructedSignature, discoveryState, interopReferences); } } } + /// + /// Tries to track a constructed generic Windows Runtime interface type. + /// + /// The for the constructed type to analyze. + /// The discovery state for this invocation. + /// The instance to use. + private static void TryTrackWindowsRuntimeGenericInterfaceTypeInstance( + GenericInstanceTypeSignature typeSignature, + InteropGeneratorDiscoveryState discoveryState, + InteropReferences interopReferences) + { + if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IEnumerator1)) + { + discoveryState.TrackIEnumerator1Type(typeSignature); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IEnumerable1)) + { + discoveryState.TrackIEnumerable1Type(typeSignature); + + // We need special handling for 'IEnumerator' types whenever we discover any constructed 'IEnumerable' + // type. This ensures that we're never missing any 'IEnumerator' instantiation, which we might depend on + // from other generated code, or projections. This special handling is needed because unlike with the other + // interfaces, 'IEnumerator' will not show up as a base interface for other collection interface types. + discoveryState.TrackIEnumerator1Type(interopReferences.IEnumerator1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IList1)) + { + discoveryState.TrackIList1Type(typeSignature); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyList1)) + { + discoveryState.TrackIReadOnlyList1Type(typeSignature); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IDictionary2)) + { + discoveryState.TrackIDictionary2Type(typeSignature); + + // When discovering dictionary types, make sure to also track 'KeyValuePair' types. Those will + // be needed when generating code for 'IEnumerator>' types, which will be discovered + // automatically. However, the same is not true the constructed 'KeyValuePair' types themselves. + // This is for the same reason why we need the other special cases in this method: members are not analyzed. + discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments])); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyDictionary2)) + { + discoveryState.TrackIReadOnlyDictionary2Type(typeSignature); + + // Same handling as above for constructed 'KeyValuePair' types + discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments])); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IObservableVector1)) + { + discoveryState.TrackIObservableVector1Type(typeSignature); + + // We need special handling for constructed 'VectorChangedEventHandler' types, as those are required for each + // discovered 'IObservableVector' type. These are not necessarily discovered in the same way, as while we are + // recursively constructing interfaces, we don't have the same logic for delegate types (or for types used in + // any signature of interface members). Because we only need this delegate type and the one below, we can just + // special case it. That is, we manually construct it every time we discover a constructed 'IObservableVector'. + discoveryState.TrackGenericDelegateType(interopReferences.VectorChangedEventHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IObservableMap2)) + { + discoveryState.TrackIObservableMap2Type(typeSignature); + + // Same handling as above for 'MapChangedEventHandler' types + discoveryState.TrackGenericDelegateType(interopReferences.MapChangedEventHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments])); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IMapChangedEventArgs1)) + { + discoveryState.TrackIMapChangedEventArgs1Type(typeSignature); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncActionWithProgress1)) + { + discoveryState.TrackIAsyncActionWithProgress1Type(typeSignature); + + // Ensure that the delegate types for this instantiation of 'IAsyncActionWithProgress' are also tracked. + // Same rationale as above for the other special cased types. Same below as well for the other async info types. + discoveryState.TrackGenericDelegateType(interopReferences.AsyncActionProgressHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); + discoveryState.TrackGenericDelegateType(interopReferences.AsyncActionWithProgressCompletedHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncOperation1)) + { + discoveryState.TrackIAsyncOperation1Type(typeSignature); + + // Same handling as above for 'AsyncOperationCompletedHandler' + discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationCompletedHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); + } + else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncOperationWithProgress2)) + { + discoveryState.TrackIAsyncOperationWithProgress2Type(typeSignature); + + // Same handling as above for 'AsyncOperationProgressHandler' and 'AsyncOperationWithProgressCompletedHandler' + discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationProgressHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments])); + discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationWithProgressCompletedHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments])); + } + } + /// /// Tries to track a constructed generic user-defined type. /// diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs index 3a6f8b9a9..4a98a1f63 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs @@ -158,7 +158,7 @@ public static void TryTrackExposedUserDefinedType( // So the discovery logic for generic instantiations below would otherwise miss it. if (interfaceSignature is GenericInstanceTypeSignature constructedSignature) { - discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences); + TryTrackWindowsRuntimeGenericInterfaceTypeInstance(constructedSignature, discoveryState, interopReferences); } } else if (interfaceDefinition.IsGeneratedComInterfaceType) From bf91476dd9b06427dba253ddaf8c18f7ddca2133 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Dec 2025 13:03:43 -0800 Subject: [PATCH 3/5] Remove InteropGeneratorDiscoveryStateExtensions Deleted the InteropGeneratorDiscoveryStateExtensions.cs file, which contained extension methods for tracking generic interface and delegate types in the interop generator discovery process. This may be part of a refactor or cleanup to simplify or relocate this logic. --- ...nteropGeneratorDiscoveryStateExtensions.cs | 110 ------------------ 1 file changed, 110 deletions(-) delete mode 100644 src/WinRT.Interop.Generator/Extensions/InteropGeneratorDiscoveryStateExtensions.cs diff --git a/src/WinRT.Interop.Generator/Extensions/InteropGeneratorDiscoveryStateExtensions.cs b/src/WinRT.Interop.Generator/Extensions/InteropGeneratorDiscoveryStateExtensions.cs deleted file mode 100644 index eea7e77ed..000000000 --- a/src/WinRT.Interop.Generator/Extensions/InteropGeneratorDiscoveryStateExtensions.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using AsmResolver.DotNet.Signatures; -using WindowsRuntime.InteropGenerator.Generation; -using WindowsRuntime.InteropGenerator.References; - -namespace WindowsRuntime.InteropGenerator; - -/// -/// Extensions for . -/// -internal static class InteropGeneratorDiscoveryStateExtensions -{ - /// - /// Tracks a generic interface type of any projected or custom-mapped type. - /// - /// The current instance. - /// The generic interface type. - /// The instance to use. - public static void TrackGenericInterfaceType( - this InteropGeneratorDiscoveryState discoveryState, - GenericInstanceTypeSignature typeSignature, - InteropReferences interopReferences) - { - if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IEnumerator1)) - { - discoveryState.TrackIEnumerator1Type(typeSignature); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IEnumerable1)) - { - discoveryState.TrackIEnumerable1Type(typeSignature); - - // We need special handling for 'IEnumerator' types whenever we discover any constructed 'IEnumerable' - // type. This ensures that we're never missing any 'IEnumerator' instantiation, which we might depend on - // from other generated code, or projections. This special handling is needed because unlike with the other - // interfaces, 'IEnumerator' will not show up as a base interface for other collection interface types. - discoveryState.TrackIEnumerator1Type(interopReferences.IEnumerator1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IList1)) - { - discoveryState.TrackIList1Type(typeSignature); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyList1)) - { - discoveryState.TrackIReadOnlyList1Type(typeSignature); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IDictionary2)) - { - discoveryState.TrackIDictionary2Type(typeSignature); - - // When discovering dictionary types, make sure to also track 'KeyValuePair' types. Those will - // be needed when generating code for 'IEnumerator>' types, which will be discovered - // automatically. However, the same is not true the constructed 'KeyValuePair' types themselves. - // This is for the same reason why we need the other special cases in this method: members are not analyzed. - discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments])); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyDictionary2)) - { - discoveryState.TrackIReadOnlyDictionary2Type(typeSignature); - - // Same handling as above for constructed 'KeyValuePair' types - discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments])); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IObservableVector1)) - { - discoveryState.TrackIObservableVector1Type(typeSignature); - - // We need special handling for constructed 'VectorChangedEventHandler' types, as those are required for each - // discovered 'IObservableVector' type. These are not necessarily discovered in the same way, as while we are - // recursively constructing interfaces, we don't have the same logic for delegate types (or for types used in - // any signature of interface members). Because we only need this delegate type and the one below, we can just - // special case it. That is, we manually construct it every time we discover a constructed 'IObservableVector'. - discoveryState.TrackGenericDelegateType(interopReferences.VectorChangedEventHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IObservableMap2)) - { - discoveryState.TrackIObservableMap2Type(typeSignature); - - // Same handling as above for 'MapChangedEventHandler' types - discoveryState.TrackGenericDelegateType(interopReferences.MapChangedEventHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments])); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IMapChangedEventArgs1)) - { - discoveryState.TrackIMapChangedEventArgs1Type(typeSignature); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncActionWithProgress1)) - { - discoveryState.TrackIAsyncActionWithProgress1Type(typeSignature); - - // Ensure that the delegate types for this instantiation of 'IAsyncActionWithProgress' are also tracked. - // Same rationale as above for the other special cased types. Same below as well for the other async info types. - discoveryState.TrackGenericDelegateType(interopReferences.AsyncActionProgressHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); - discoveryState.TrackGenericDelegateType(interopReferences.AsyncActionWithProgressCompletedHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncOperation1)) - { - discoveryState.TrackIAsyncOperation1Type(typeSignature); - - discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationCompletedHandler1.MakeGenericReferenceType([.. typeSignature.TypeArguments])); - } - else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IAsyncOperationWithProgress2)) - { - discoveryState.TrackIAsyncOperationWithProgress2Type(typeSignature); - - discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationProgressHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments])); - discoveryState.TrackGenericDelegateType(interopReferences.AsyncOperationWithProgressCompletedHandler2.MakeGenericReferenceType([.. typeSignature.TypeArguments])); - } - } -} \ No newline at end of file From c083109cdbaeec877d86816cb6a4d202b9e05305 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Dec 2025 13:31:26 -0800 Subject: [PATCH 4/5] Enhance generic type discovery for dictionary collections Updated generic type discovery logic to explicitly track constructed DictionaryKeyCollection, DictionaryValueCollection, ReadOnlyDictionaryKeyCollection, and ReadOnlyDictionaryValueCollection types when discovering IDictionary and IReadOnlyDictionary instantiations. This ensures correct marshaling of these types, which may not be present in input assemblies but are generated during emit. Also refactored method signatures to consistently pass the current module and args where needed. --- .../InteropTypeDiscovery.Generics.cs | 95 +++++++++++++++---- .../Discovery/InteropTypeDiscovery.cs | 11 ++- .../Generation/InteropGenerator.Discover.cs | 3 +- 3 files changed, 86 insertions(+), 23 deletions(-) diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs index a31e29814..618452e37 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs @@ -58,22 +58,23 @@ public static void TryTrackGenericTypeInstance( if (typeSignature.IsWindowsRuntimeType(interopReferences)) { TryTrackWindowsRuntimeGenericTypeInstance( - typeDefinition, - typeSignature, - args, - discoveryState, - interopReferences, - module); + typeDefinition: typeDefinition, + typeSignature: typeSignature, + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); } else { // Otherwise, try to track information for some constructed managed type TryTrackManagedGenericTypeInstance( - typeDefinition, - typeSignature, - args, - discoveryState, - interopReferences); + typeDefinition: typeDefinition, + typeSignature: typeSignature, + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); } } @@ -165,7 +166,12 @@ private static void TryTrackWindowsRuntimeGenericTypeInstance( // Track all projected Windows Runtime generic interfaces if (typeDefinition.IsInterface) { - TryTrackWindowsRuntimeGenericInterfaceTypeInstance(typeSignature, discoveryState, interopReferences); + TryTrackWindowsRuntimeGenericInterfaceTypeInstance( + typeSignature: typeSignature, + args: args, + discoveryState, + interopReferences: interopReferences, + module: module); // We also want to crawl base interfaces foreach (TypeSignature interfaceSignature in typeSignature.EnumerateAllInterfaces()) @@ -191,7 +197,12 @@ private static void TryTrackWindowsRuntimeGenericTypeInstance( continue; } - TryTrackWindowsRuntimeGenericInterfaceTypeInstance(constructedSignature, discoveryState, interopReferences); + TryTrackWindowsRuntimeGenericInterfaceTypeInstance( + typeSignature: constructedSignature, + args: args, + discoveryState, + interopReferences: interopReferences, + module: module); } } } @@ -200,12 +211,16 @@ private static void TryTrackWindowsRuntimeGenericTypeInstance( /// Tries to track a constructed generic Windows Runtime interface type. /// /// The for the constructed type to analyze. + /// The arguments for this invocation. /// The discovery state for this invocation. /// The instance to use. + /// The module currently being analyzed. private static void TryTrackWindowsRuntimeGenericInterfaceTypeInstance( GenericInstanceTypeSignature typeSignature, + InteropGeneratorArgs args, InteropGeneratorDiscoveryState discoveryState, - InteropReferences interopReferences) + InteropReferences interopReferences, + ModuleDefinition module) { if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IEnumerator1)) { @@ -238,6 +253,27 @@ private static void TryTrackWindowsRuntimeGenericInterfaceTypeInstance( // automatically. However, the same is not true the constructed 'KeyValuePair' types themselves. // This is for the same reason why we need the other special cases in this method: members are not analyzed. discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments])); + + // When we discover a constructed 'IDictionary' instantiation, we'll be generating a native object type during + // the emit phase, which is used to marshal anonymous objects. This derives from 'WindowsRuntimeDictionary'. + // For the 'Keys' and 'Values' properties, that base type will return instances of the 'DictionaryKeyCollection' + // and 'DictionaryValueCollection' types, respectively. Those instantiations will not be seen by 'cswinrtgen', + // because they will only exist in the final 'WinRT.Interop.dll' assembly being generated, and not in any input assemblies. + // So to ensure that we can still correctly marshal those to native, if needed, we manually track them as if we had seen them. + TryTrackGenericTypeInstance( + typeSignature: interopReferences.DictionaryKeyCollection2.MakeGenericReferenceType([.. typeSignature.TypeArguments]), + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); + + // Handle 'DictionaryValueCollection' as well + TryTrackGenericTypeInstance( + typeSignature: interopReferences.DictionaryValueCollection2.MakeGenericReferenceType([.. typeSignature.TypeArguments]), + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); } else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyDictionary2)) { @@ -245,6 +281,22 @@ private static void TryTrackWindowsRuntimeGenericInterfaceTypeInstance( // Same handling as above for constructed 'KeyValuePair' types discoveryState.TrackKeyValuePairType(interopReferences.KeyValuePair2.MakeGenericValueType([.. typeSignature.TypeArguments])); + + // Handle 'ReadOnlyDictionaryKeyCollection' as above + TryTrackGenericTypeInstance( + typeSignature: interopReferences.ReadOnlyDictionaryKeyCollection2.MakeGenericReferenceType([.. typeSignature.TypeArguments]), + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); + + // Handle 'ReadOnlyDictionaryValueCollection' as well + TryTrackGenericTypeInstance( + typeSignature: interopReferences.ReadOnlyDictionaryValueCollection2.MakeGenericReferenceType([.. typeSignature.TypeArguments]), + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); } else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IObservableVector1)) { @@ -302,12 +354,14 @@ private static void TryTrackWindowsRuntimeGenericInterfaceTypeInstance( /// The arguments for this invocation. /// The discovery state for this invocation. /// The instance to use. + /// The module currently being analyzed. private static void TryTrackManagedGenericTypeInstance( TypeDefinition typeDefinition, GenericInstanceTypeSignature typeSignature, InteropGeneratorArgs args, InteropGeneratorDiscoveryState discoveryState, - InteropReferences interopReferences) + InteropReferences interopReferences, + ModuleDefinition module) { // Check for all '[ReadOnly]Span' types in particular, and track them as SZ array types. // This is because "pass-array" and "fill-array" parameters are projected using spans, but @@ -325,10 +379,11 @@ private static void TryTrackManagedGenericTypeInstance( // Otherwise, try to track a constructed user-defined type TryTrackExposedUserDefinedType( - typeDefinition, - typeSignature, - args, - discoveryState, - interopReferences); + typeDefinition: typeDefinition, + typeSignature: typeSignature, + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); } } \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs index 4a98a1f63..9b94e0a2e 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs @@ -69,6 +69,7 @@ public static void TryTrackTypeHierarchyType( /// The arguments for this invocation. /// The discovery state for this invocation. /// The instance to use. + /// The module currently being analyzed. /// /// This method expects to either be non-generic, or /// to have be a fully constructed signature for it. @@ -78,7 +79,8 @@ public static void TryTrackExposedUserDefinedType( TypeSignature typeSignature, InteropGeneratorArgs args, InteropGeneratorDiscoveryState discoveryState, - InteropReferences interopReferences) + InteropReferences interopReferences, + ModuleDefinition module) { // Ignore types that should explicitly be excluded if (TypeExclusions.IsExcluded(typeDefinition, interopReferences)) @@ -158,7 +160,12 @@ public static void TryTrackExposedUserDefinedType( // So the discovery logic for generic instantiations below would otherwise miss it. if (interfaceSignature is GenericInstanceTypeSignature constructedSignature) { - TryTrackWindowsRuntimeGenericInterfaceTypeInstance(constructedSignature, discoveryState, interopReferences); + TryTrackWindowsRuntimeGenericInterfaceTypeInstance( + typeSignature: constructedSignature, + args: args, + discoveryState, + interopReferences: interopReferences, + module: module); } } else if (interfaceDefinition.IsGeneratedComInterfaceType) diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index 688968d69..b8bb3f0e8 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -192,7 +192,8 @@ private static void DiscoverExposedUserDefinedTypes( typeSignature: type.ToTypeSignature(), args: args, discoveryState: discoveryState, - interopReferences: interopReferences); + interopReferences: interopReferences, + module: module); } } catch (Exception e) From dcc032dfee1bcaafc8f65a1b06d39d73a961d5d0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Thu, 15 Jan 2026 13:56:07 -0800 Subject: [PATCH 5/5] Replace thread-static builder with pooled builder for type discovery Switched from a thread-static TypeSignatureEquatableSet.Builder to a ConcurrentBag-based pool to handle recursive calls during interface discovery. This avoids conflicts and improves resource reuse when analyzing types that may trigger nested discovery logic. --- .../Discovery/InteropTypeDiscovery.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs index 9b94e0a2e..94ab9b7bb 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Concurrent; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; using WindowsRuntime.InteropGenerator.Errors; @@ -18,10 +19,9 @@ namespace WindowsRuntime.InteropGenerator.Discovery; internal static partial class InteropTypeDiscovery { /// - /// A thread-local instance that can be reused by discovery logic. + /// A pool of instances that can be reused by discovery logic. /// - [ThreadStatic] - private static TypeSignatureEquatableSet.Builder? TypeSignatures; + private static readonly ConcurrentBag TypeSignatureBuilderPool = []; /// /// Tries to track a given composable Windows Runtime type. @@ -116,8 +116,17 @@ public static void TryTrackExposedUserDefinedType( return; } - // Reuse the thread-local builder to track all implemented interfaces for the current type - TypeSignatureEquatableSet.Builder interfaces = TypeSignatures ??= new TypeSignatureEquatableSet.Builder(); + // Reuse a builder to track all implemented interfaces for the current type, or create a new one. + // Note, we can't just have a single thread-local builder that we reuse here, as this method might + // be called recursively in some scenarios. For instance, consider a type that implements a generic + // interface such as 'IList' (some constructed instantiation of it). As part of the interface + // discovery logic, we'll also be tracking the additional 'ReadOnlyCollection' type, as that's + // needed from the 'IListAdapter.GetView' method. That type will itself be analyzed here just + // like any other user-define type, and so on. So the pool is needed to avoid creating conflicts. + if (!TypeSignatureBuilderPool.TryTake(out TypeSignatureEquatableSet.Builder? interfaces)) + { + interfaces = new TypeSignatureEquatableSet.Builder(); + } // Since we're reusing the builder for all types, make sure to clear it first interfaces.Clear(); @@ -209,5 +218,8 @@ public static void TryTrackExposedUserDefinedType( { discoveryState.TrackUserDefinedType(typeSignature, interfaces.ToEquatableSet()); } + + // Return the builder to the pool for reuse + TypeSignatureBuilderPool.Add(interfaces); } } \ No newline at end of file