From fb317f7ea2586d9fd3f5a3f3a60c4c9e95550b99 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 13 Dec 2025 11:24:30 -0800 Subject: [PATCH 01/18] Add method to enumerate all implemented interfaces Introduces EnumerateAllInterfaces to TypeSignatureExtensions, allowing traversal of all interfaces implemented by a type, including those from base types. This method handles generic types and ensures robust traversal even with incomplete type hierarchies. --- .../Extensions/TypeSignatureExtensions.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs b/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs index be76c72f1..fafd04cc3 100644 --- a/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs +++ b/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Metadata.Tables; namespace WindowsRuntime.InteropGenerator; @@ -43,5 +45,64 @@ public bool IsFullyResolvable([NotNullWhen(true)] out TypeDefinition? definition return true; } + + /// + /// Enumerates all interface types implementation by the specified type, including those implemented by base types. + /// + /// The sequence of interface types implemented by the input type. + /// + /// This method might return the same interface types multiple times, if implemented by multiple types in the hierarchy. + /// + public IEnumerable EnumerateAllInterfaces() + { + TypeSignature currentSignature = signature; + + while (currentSignature is not null) + { + // If we can't resolve the current type signature, we have to stop. + // Callers should validate the type hierarchy before calling this. + if (!currentSignature.IsFullyResolvable(out TypeDefinition? currentDefinition)) + { + yield break; + } + + GenericContext context = new(currentSignature as GenericInstanceTypeSignature, null); + + // Go over all interfaces implemented on the current type. We don't need + // to recurse on them, as classes always declare the full transitive set. + foreach (InterfaceImplementation interfaceImplementation in currentDefinition.Interfaces) + { + // Ignore this interface if we can't actually retrieve the interface type. + // This should never happen for valid .NET assemblies, but just in case. + if (interfaceImplementation.Interface?.ToReferenceTypeSignature() is not TypeSignature interfaceSignature) + { + continue; + } + + // Return either the current non-generic interface, or the constructed generic one. + // We don't have to check: if the interface is not generic, this will be a no-op. + yield return interfaceSignature.InstantiateGenericTypes(context); + + // Also recurse on the base interfaces + foreach (TypeSignature baseInterface in interfaceSignature.EnumerateAllInterfaces()) + { + yield return baseInterface.InstantiateGenericTypes(context); + } + } + + ITypeDefOrRef? baseType = currentDefinition.BaseType; + + // Stop if we have no available base type or if we reached 'object' + if (baseType is null or CorLibTypeSignature { ElementType: ElementType.Object }) + { + yield break; + } + + // Get the signature for the base type, adding back any generic context. + // Note that the base type will always be a reference type, even for + // struct types (in that case, the base type will be 'System.ValueType'). + currentSignature = baseType.ToReferenceTypeSignature().InstantiateGenericTypes(context); + } + } } } \ No newline at end of file From 2f3823cdacb879179ad8f8fe9772ff9e66d255ef Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 13 Dec 2025 11:37:52 -0800 Subject: [PATCH 02/18] Avoid redundant instantiation of base interfaces Updated the logic to yield base interfaces directly without re-instantiating their generic types, as they are already instantiated when returned. This prevents unnecessary operations and improves code clarity. --- .../Extensions/TypeSignatureExtensions.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs b/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs index fafd04cc3..cc0a7fbcc 100644 --- a/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs +++ b/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs @@ -83,10 +83,11 @@ public IEnumerable EnumerateAllInterfaces() // We don't have to check: if the interface is not generic, this will be a no-op. yield return interfaceSignature.InstantiateGenericTypes(context); - // Also recurse on the base interfaces + // Also recurse on the base interfaces (no need to instantiate the returned interface type + // signatures for base interfaces here: they will be already instantiate when returned). foreach (TypeSignature baseInterface in interfaceSignature.EnumerateAllInterfaces()) { - yield return baseInterface.InstantiateGenericTypes(context); + yield return baseInterface; } } From 2ef5113100611d022e5400f228030e86f2acdd5f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 14 Dec 2025 12:12:10 -0800 Subject: [PATCH 03/18] Track type definition in IObservableVector1 builder Added a call to emitState.TrackTypeDefinition for implType and vectorType in the InteropTypeDefinitionBuilder.IObservableVector1 implementation. This ensures the type is tracked, which may be required for COM interface entries involving user-defined types. --- .../InteropTypeDefinitionBuilder.IObservableVector1.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IObservableVector1.cs b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IObservableVector1.cs index 215b6a7cc..d70160642 100644 --- a/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IObservableVector1.cs +++ b/src/WinRT.Interop.Generator/Builders/InteropTypeDefinitionBuilder.IObservableVector1.cs @@ -486,6 +486,9 @@ public static void ImplType( implType.Methods.Add(makeVectorChangedMethod); implType.Methods.Add(get_VectorChangedTableMethod); implType.Properties.Add(vectorChangedTableProperty); + + // Track the type (it may be needed by COM interface entries for user-defined types) + emitState.TrackTypeDefinition(implType, vectorType, "Impl"); } /// From 61ee8f92d1b6c9e0ed92cdd97cabb5df1278d40a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 14 Dec 2025 12:47:11 -0800 Subject: [PATCH 04/18] Refactor and clarify interface resolution warnings Renamed and updated warning methods in WellKnownInteropExceptions for improved clarity regarding interface and base type resolution failures. Refactored InteropGenerator.Discover.cs to use the new warning methods, streamline interface discovery, and ensure correct handling of generic interface instantiations. --- .../Errors/WellKnownInteropExceptions.cs | 20 ++----- .../Generation/InteropGenerator.Discover.cs | 56 +++++++++++-------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs index 42357863e..50d273efe 100644 --- a/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs +++ b/src/WinRT.Interop.Generator/Errors/WellKnownInteropExceptions.cs @@ -430,11 +430,11 @@ public static WellKnownInteropException CustomMappedTypeComWrappersMarshallerAtt } /// - /// Failed to resolve a '[GeneratedComInterface]' type. + /// Failed to resolve the type of an implemented interface. /// - public static WellKnownInteropWarning GeneratedComInterfaceTypeNotResolvedWarning(TypeSignature interfaceType, TypeDefinition type) + public static WellKnownInteropWarning InterfaceImplementationTypeNotResolvedWarning(TypeSignature interfaceType, TypeDefinition type) { - return Warning(49, $"Failed to resolve the '[GeneratedComInterface]' type '{interfaceType}' while processing type '{type}': the interface will not be included in the set of available COM interface entries."); + return Warning(49, $"Failed to resolve interface type '{interfaceType}' while processing type '{type}': the interface will not be included in the set of available COM interface entries."); } /// @@ -534,11 +534,11 @@ public static WellKnownInteropWarning GeneratedComInterfaceGuidAttributeNotFound } /// - /// Failed to resolve a Windows Runtime interface type. + /// Failed to resolve a base type for a user-defined type. /// - public static WellKnownInteropWarning WindowsRuntimeInterfaceTypeNotResolvedWarning(TypeSignature interfaceType, TypeDefinition type) + public static WellKnownInteropWarning UserDefinedTypeNotFullyResolvedWarning(ITypeDefOrRef baseType, TypeDefinition type) { - return Warning(62, $"Failed to resolve the Windows Runtime interface type '{interfaceType}' while processing type '{type}': the interface will not be included in the set of available COM interface entries."); + return Warning(62, $"Failed to resolve the base type '{baseType}' in the type hierarchy for user-defined type '{type}': marshalling code for it will not be generated."); } /// @@ -573,14 +573,6 @@ public static WellKnownInteropWarning WindowsRuntimeClassTypeNotResolvedWarning( return Warning(66, $"Failed to resolve the base type '{baseType}' for Windows Runtime class type '{classType}': runtime casts to the base type will not work if the type is trimmed."); } - /// - /// Failed to resolve a base type for a user-defined type. - /// - public static WellKnownInteropWarning UserDefinedTypeNotFullyResolvedWarning(ITypeDefOrRef baseType, TypeDefinition type) - { - return Warning(67, $"Failed to resolve the base type '{baseType}' in the type hierarchy for user-defined type '{type}': marshalling code for it will not be generated."); - } - /// /// Creates a new exception with the specified id and message. /// diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index a417b9d33..6cc499d42 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -249,41 +249,42 @@ private static void DiscoverExposedUserDefinedTypes( bool hasAnyProjectedWindowsRuntimeInterfaces = false; // Gather all implemented Windows Runtime interfaces for the current type - foreach (InterfaceImplementation implementation in type.EnumerateAllInterfaces()) + foreach (TypeSignature interfaceSignature in type.ToTypeSignature().EnumerateAllInterfaces()) { - // If the current implementation has no valid interface, skip it. - // This should never really happen for valid .NET assemblies. - if (implementation.Interface?.ToReferenceTypeSignature() is not TypeSignature interfaceSignature) + // Make sure we can resolve the interface type fully, which we should always be able to do. + // This can really only fail for some constructed generics, for invalid type arguments. + if (!interfaceSignature.IsFullyResolvable(out TypeDefinition? interfaceDefinition)) { + WellKnownInteropExceptions.InterfaceImplementationTypeNotResolvedWarning(interfaceSignature, type).LogOrThrow(args.TreatWarningsAsErrors); + continue; } // Check for projected Windows Runtime interfaces first if (interfaceSignature.IsWindowsRuntimeType(interopReferences)) { - // Make sure we can resolve the interface type fully, which we should always be able to do. - // This can really only fail for some constructed generics, for invalid type arguments. - if (!interfaceSignature.IsFullyResolvable(out _)) - { - WellKnownInteropExceptions.WindowsRuntimeInterfaceTypeNotResolvedWarning(interfaceSignature, type).LogOrThrow(args.TreatWarningsAsErrors); - - continue; - } - hasAnyProjectedWindowsRuntimeInterfaces = true; interfaces.Add(interfaceSignature); - } - else if (implementation.Interface.IsGeneratedComInterfaceType) - { - // To properly track '[GeneratedComInterface]' implementations, we need to be able to resolve those interface types - if (!implementation.Interface.IsFullyResolvable(out TypeDefinition? interfaceDefinition)) - { - WellKnownInteropExceptions.GeneratedComInterfaceTypeNotResolvedWarning(interfaceSignature, type).LogOrThrow(args.TreatWarningsAsErrors); - continue; + // If the current interface is generic, also make sure that it's tracked. This is needed + // to fully cover all possible constructed generic interface types that might be needed. + // For instance, consider this case: + // + // class A : IEnumerable; + // class B : A; + // + // While processing 'B', we'll discover the constructed 'IEnumerable' interface. + // This interface would not have been discovered when processing 'A', as it's not + // in the 'TypeSpec' metadata table, and only appears as unconstructed on 'A'. + // So the discovery logic for generic instantiations below would otherwise miss it. + if (interfaceSignature is GenericInstanceTypeSignature constructedSignature) + { + discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences); } - + } + else if (interfaceDefinition.IsGeneratedComInterfaceType) + { // We can only gather this type if we can find the generated 'InterfaceInformation' type. // If we can't find it, we can't add the interface to the list of interface entries. We // should warn if that's the (unlikely) case, so users can at least know that something @@ -416,8 +417,15 @@ private static void DiscoverGenericTypeInstantiations( discoveryState.TrackGenericInterfaceType(typeSignature, interopReferences); // We also want to crawl base interfaces - foreach (GenericInstanceTypeSignature interfaceSignature in typeDefinition.EnumerateGenericInstanceInterfaceSignatures(typeSignature)) + foreach (TypeSignature interfaceSignature in typeSignature.EnumerateAllInterfaces()) { + // Filter out just constructed generic interfaces, since we only care about those here. + // The non-generic ones are only useful when gathering interfaces for user-defined types. + if (interfaceSignature is not GenericInstanceTypeSignature constructedSignature) + { + continue; + } + if (!interfaceSignature.IsFullyResolvable(out _)) { // Also log a warning the first time we fail to resolve one of the recursively discovered generic @@ -432,7 +440,7 @@ private static void DiscoverGenericTypeInstantiations( continue; } - discoveryState.TrackGenericInterfaceType(interfaceSignature, interopReferences); + discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences); } continue; From 9516c71ba5e9575b92539d4e9096019d6aace5ac Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 14 Dec 2025 12:47:17 -0800 Subject: [PATCH 05/18] Remove EnumerateAllInterfaces method from TypeDefinitionExtensions Deleted the EnumerateAllInterfaces method, which enumerated all interface implementations for a type and its base types. This cleanup may be due to redundancy, lack of use, or a refactor in interface enumeration logic. --- .../Extensions/TypeDefinitionExtensions.cs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/WinRT.Interop.Generator/Extensions/TypeDefinitionExtensions.cs b/src/WinRT.Interop.Generator/Extensions/TypeDefinitionExtensions.cs index 515518250..d03c841f6 100644 --- a/src/WinRT.Interop.Generator/Extensions/TypeDefinitionExtensions.cs +++ b/src/WinRT.Interop.Generator/Extensions/TypeDefinitionExtensions.cs @@ -241,24 +241,6 @@ public IEnumerable EnumerateBaseTypesAndSelf() } } - /// - /// Enumerates all interface types implementation by the specified type, including those implemented by base types. - /// - /// The sequence of interface types implemented by the input type. - /// - /// This method might return the same interface types multiple times, if implemented by multiple types in the hierarchy. - /// - public IEnumerable EnumerateAllInterfaces() - { - foreach (TypeDefinition currentType in type.EnumerateBaseTypesAndSelf()) - { - foreach (InterfaceImplementation implementation in currentType.Interfaces) - { - yield return implementation; - } - } - } - /// /// Enumerates all generic instance type signatures for base interfaces, from a given starting interface. /// From b3340a6338db43f30a0c05190a46a8fc26e8c311 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 12:13:10 -0800 Subject: [PATCH 06/18] Refactor base type checks in type hierarchy utilities Introduced HasBaseType extension to encapsulate base type checks and refactored related logic in TypeDefinitionExtensions and InteropGenerator.Discover. This improves code clarity and reduces duplication when handling type hierarchies and base type resolution. --- .../Extensions/TypeDefinitionExtensions.cs | 29 ++++++++++++------- .../Generation/InteropGenerator.Discover.cs | 6 ++-- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/WinRT.Interop.Generator/Extensions/TypeDefinitionExtensions.cs b/src/WinRT.Interop.Generator/Extensions/TypeDefinitionExtensions.cs index d03c841f6..e831a8e68 100644 --- a/src/WinRT.Interop.Generator/Extensions/TypeDefinitionExtensions.cs +++ b/src/WinRT.Interop.Generator/Extensions/TypeDefinitionExtensions.cs @@ -25,6 +25,17 @@ internal static class TypeDefinitionExtensions /// public bool IsStatic => type.IsAbstract && type.IsSealed; + /// + /// Gets whether a given type has a base type other than . + /// + /// The resulting base type, if available. + public bool HasBaseType([NotNullWhen(true)] out ITypeDefOrRef? baseType) + { + baseType = type.BaseType; + + return baseType is not (null or CorLibTypeSignature { ElementType: ElementType.Object }); + } + /// /// Gets a value indicating whether a given 's type hierarchy can be fully resolved to type definitions. /// @@ -32,18 +43,16 @@ internal static class TypeDefinitionExtensions /// Whether the input 's type hierarchy can be fully resolved to type definitions. public bool IsTypeHierarchyFullyResolvable([NotNullWhen(false)] out ITypeDefOrRef? failedResolutionBaseType) { - ITypeDefOrRef? baseType = type.BaseType; + TypeDefinition currentDefinition = type; - while (baseType is not (null or CorLibTypeSignature { ElementType: ElementType.Object })) + while (currentDefinition.HasBaseType(out ITypeDefOrRef? baseType)) { - if (!baseType.IsFullyResolvable(out TypeDefinition? baseDefinition)) + if (!baseType.IsFullyResolvable(out currentDefinition!)) { failedResolutionBaseType = baseType; return false; } - - baseType = baseDefinition.BaseType; } failedResolutionBaseType = null; @@ -224,20 +233,18 @@ public IEnumerable EnumerateBaseTypesAndSelf() { yield return type; - ITypeDefOrRef? baseType = type.BaseType; + TypeDefinition currentDefinition = type; - while (baseType is not (null or CorLibTypeSignature { ElementType: ElementType.Object })) + while (currentDefinition.HasBaseType(out ITypeDefOrRef? baseType)) { // If we can't resolve the current base type, we have to stop. // Callers should validate the type hierarchy before calling this. - if (!baseType.IsFullyResolvable(out TypeDefinition? baseDefinition)) + if (!baseType.IsFullyResolvable(out currentDefinition!)) { yield break; } - yield return baseDefinition; - - baseType = baseDefinition.BaseType; + yield return currentDefinition; } } diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index 6cc499d42..ec6c09145 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -162,15 +162,15 @@ private static void DiscoverTypeHierarchyTypes( } // Ignore types that don't have another base class - if (type.BaseType is null || SignatureComparer.IgnoreVersion.Equals(type.BaseType, module.CorLibTypeFactory.Object)) + if (!type.HasBaseType(out ITypeDefOrRef? baseType)) { continue; } // We need to resolve the base type to be able to look up attributes on it - if (!type.BaseType.IsFullyResolvable(out TypeDefinition? baseType)) + if (!baseType.IsFullyResolvable(out TypeDefinition? baseDefinition)) { - WellKnownInteropExceptions.WindowsRuntimeClassTypeNotResolvedWarning(type.BaseType, type).LogOrThrow(args.TreatWarningsAsErrors); + WellKnownInteropExceptions.WindowsRuntimeClassTypeNotResolvedWarning(baseType, type).LogOrThrow(args.TreatWarningsAsErrors); continue; } From 732e9723bf13421f17dec628b4139bca513d7986 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 12:29:30 -0800 Subject: [PATCH 07/18] Refactor user-defined type discovery logic Moved the logic for tracking exposed user-defined types from InteropGenerator.Discover.cs into a new InteropTypeDiscovery helper class. This improves code organization and reusability by encapsulating the discovery process in a dedicated static method. --- .../Discovery/InteropTypeDiscovery.cs | 156 ++++++++++++++++++ .../Generation/InteropGenerator.Discover.cs | 115 +------------ 2 files changed, 164 insertions(+), 107 deletions(-) create mode 100644 src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs new file mode 100644 index 000000000..b31da198e --- /dev/null +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.InteropGenerator.Errors; +using WindowsRuntime.InteropGenerator.Generation; +using WindowsRuntime.InteropGenerator.Models; +using WindowsRuntime.InteropGenerator.References; + +namespace WindowsRuntime.InteropGenerator.Discovery; + +/// +/// A discovery helper type for interop types. +/// +internal static partial class InteropTypeDiscovery +{ + /// + /// A thread-local instance that can be reused by discovery logic. + /// + [ThreadStatic] + private static TypeSignatureEquatableSet.Builder? TypeSignatures; + + /// + /// Tries to track an exposed user-defined type. + /// + /// The for the type to analyze. + /// The for the type to analyze. + /// The arguments for this invocation. + /// The discovery state for this invocation. + /// The instance to use. + /// + /// This method expects to either be non-generic, or + /// to have be a fully constructed signature for it. + /// + public static bool TryTrackExposedUserDefinedType( + TypeDefinition typeDefinition, + TypeSignature typeSignature, + InteropGeneratorArgs args, + InteropGeneratorDiscoveryState discoveryState, + InteropReferences interopReferences) + { + // We can skip all projected Windows Runtime types early, as they don't need CCW support + if (typeDefinition.IsProjectedWindowsRuntimeType) + { + return false; + } + + // We'll need to look up attributes and enumerate interfaces across the entire type + // hierarchy for this type, so make sure that we can resolve all types from it first. + if (!typeDefinition.IsTypeHierarchyFullyResolvable(out ITypeDefOrRef? failedResolutionBaseType)) + { + WellKnownInteropExceptions.UserDefinedTypeNotFullyResolvedWarning(failedResolutionBaseType, typeDefinition).LogOrThrow(args.TreatWarningsAsErrors); + + return false; + } + + // We only want to process non-generic user-defined types that are potentially exposed to Windows Runtime + if (!typeDefinition.IsPossiblyWindowsRuntimeExposedType || typeDefinition.IsWindowsRuntimeManagedOnlyType(interopReferences)) + { + return false; + } + + // Reuse the thread-local builder to track all implemented interfaces for the current type + TypeSignatureEquatableSet.Builder interfaces = TypeSignatures ??= new TypeSignatureEquatableSet.Builder(); + + // Since we're reusing the builder for all types, make sure to clear it first + interfaces.Clear(); + + // We want to explicitly track whether the type implements any projected Windows Runtime + // interfaces, as we are only interested in such types. We want to also gather all + // implemented '[GeneratedComInterface]' interfaces, but if a type only implements + // those, we will ignore it. Such types should be marshalled via 'ComWrappers' directly. + bool hasAnyProjectedWindowsRuntimeInterfaces = false; + + // Gather all implemented Windows Runtime interfaces for the current type + foreach (TypeSignature interfaceSignature in typeSignature.EnumerateAllInterfaces()) + { + // Make sure we can resolve the interface type fully, which we should always be able to do. + // This can really only fail for some constructed generics, for invalid type arguments. + if (!interfaceSignature.IsFullyResolvable(out TypeDefinition? interfaceDefinition)) + { + WellKnownInteropExceptions.InterfaceImplementationTypeNotResolvedWarning(interfaceSignature, typeDefinition).LogOrThrow(args.TreatWarningsAsErrors); + + continue; + } + + // Check for projected Windows Runtime interfaces first + if (interfaceSignature.IsWindowsRuntimeType(interopReferences)) + { + hasAnyProjectedWindowsRuntimeInterfaces = true; + + interfaces.Add(interfaceSignature); + + // If the current interface is generic, also make sure that it's tracked. This is needed + // to fully cover all possible constructed generic interface types that might be needed. + // For instance, consider this case: + // + // class A : IEnumerable; + // class B : A; + // + // While processing 'B', we'll discover the constructed 'IEnumerable' interface. + // This interface would not have been discovered when processing 'A', as it's not + // in the 'TypeSpec' metadata table, and only appears as unconstructed on 'A'. + // So the discovery logic for generic instantiations below would otherwise miss it. + if (interfaceSignature is GenericInstanceTypeSignature constructedSignature) + { + discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences); + } + } + else if (interfaceDefinition.IsGeneratedComInterfaceType) + { + // We can only gather this type if we can find the generated 'InterfaceInformation' type. + // If we can't find it, we can't add the interface to the list of interface entries. We + // should warn if that's the (unlikely) case, so users can at least know that something + // is wrong. Otherwise we'd just silently ignore these types, resulting in runtime failures. + if (!interfaceDefinition.TryGetInterfaceInformationType(interopReferences, out _)) + { + WellKnownInteropExceptions.GeneratedComInterfaceImplementationTypeNotFoundWarning(interfaceDefinition, typeDefinition).LogOrThrow(args.TreatWarningsAsErrors); + + continue; + } + + // Ensure we can get the '[GuidAttribute]' from the interface. We need this at compile time + // so we can check against some specific IID which might affect how we construct the COM + // interface entries. For instance, we need to check whether 'IMarshal' is implemented. + if (!interfaceDefinition.TryGetGuidAttribute(interopReferences, out Guid iid)) + { + WellKnownInteropExceptions.GeneratedComInterfaceGuidAttributeNotFoundWarning(interfaceDefinition, typeDefinition).LogOrThrow(args.TreatWarningsAsErrors); + + continue; + } + + // Validate that the current interface isn't trying to implement a reserved interface. + // For instance, it's not allowed to try to explicitly implement 'IUnknown' or 'IInspectable'. + if (WellKnownInterfaceIIDs.ReservedIIDsMap.TryGetValue(iid, out string? interfaceName)) + { + throw WellKnownInteropExceptions.GeneratedComInterfaceReservedGuidError(interfaceDefinition, typeDefinition, iid, interfaceName); + } + + // Also track all '[GeneratedComInterface]' interfaces too, and filter them later (below) + interfaces.Add(interfaceSignature); + } + } + + // If the user-defined type implements at least a Windows Runtime interface, then it's considered exposed. + // We don't want to handle marshalling code for types with only '[GeneratedComInterface]' interfaces. + if (hasAnyProjectedWindowsRuntimeInterfaces) + { + discoveryState.TrackUserDefinedType(typeSignature, interfaces.ToEquatableSet()); + } + + return true; + } +} \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index ec6c09145..0a089f8c8 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using AsmResolver.DotNet; using AsmResolver.DotNet.Signatures; +using WindowsRuntime.InteropGenerator.Discovery; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Models; using WindowsRuntime.InteropGenerator.References; @@ -218,113 +219,13 @@ private static void DiscoverExposedUserDefinedTypes( continue; } - // We can skip all projected Windows Runtime types early, as they don't need CCW support - if (type.IsProjectedWindowsRuntimeType) - { - continue; - } - - // We'll need to look up attributes and enumerate interfaces across the entire type - // hierarchy for this type, so make sure that we can resolve all types from it first. - if (!type.IsTypeHierarchyFullyResolvable(out ITypeDefOrRef? failedResolutionBaseType)) - { - WellKnownInteropExceptions.UserDefinedTypeNotFullyResolvedWarning(failedResolutionBaseType, type).LogOrThrow(args.TreatWarningsAsErrors); - - continue; - } - - // We only want to process non-generic user-defined types that are potentially exposed to Windows Runtime - if (!type.IsPossiblyWindowsRuntimeExposedType || type.IsWindowsRuntimeManagedOnlyType(interopReferences)) - { - continue; - } - - // Since we're reusing the builder for all types, make sure to clear it first - interfaces.Clear(); - - // We want to explicitly track whether the type implements any projected Windows Runtime - // interfaces, as we are only interested in such types. We want to also gather all - // implemented '[GeneratedComInterface]' interfaces, but if a type only implements - // those, we will ignore it. Such types should be marshalled via 'ComWrappers' directly. - bool hasAnyProjectedWindowsRuntimeInterfaces = false; - - // Gather all implemented Windows Runtime interfaces for the current type - foreach (TypeSignature interfaceSignature in type.ToTypeSignature().EnumerateAllInterfaces()) - { - // Make sure we can resolve the interface type fully, which we should always be able to do. - // This can really only fail for some constructed generics, for invalid type arguments. - if (!interfaceSignature.IsFullyResolvable(out TypeDefinition? interfaceDefinition)) - { - WellKnownInteropExceptions.InterfaceImplementationTypeNotResolvedWarning(interfaceSignature, type).LogOrThrow(args.TreatWarningsAsErrors); - - continue; - } - - // Check for projected Windows Runtime interfaces first - if (interfaceSignature.IsWindowsRuntimeType(interopReferences)) - { - hasAnyProjectedWindowsRuntimeInterfaces = true; - - interfaces.Add(interfaceSignature); - - // If the current interface is generic, also make sure that it's tracked. This is needed - // to fully cover all possible constructed generic interface types that might be needed. - // For instance, consider this case: - // - // class A : IEnumerable; - // class B : A; - // - // While processing 'B', we'll discover the constructed 'IEnumerable' interface. - // This interface would not have been discovered when processing 'A', as it's not - // in the 'TypeSpec' metadata table, and only appears as unconstructed on 'A'. - // So the discovery logic for generic instantiations below would otherwise miss it. - if (interfaceSignature is GenericInstanceTypeSignature constructedSignature) - { - discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences); - } - } - else if (interfaceDefinition.IsGeneratedComInterfaceType) - { - // We can only gather this type if we can find the generated 'InterfaceInformation' type. - // If we can't find it, we can't add the interface to the list of interface entries. We - // should warn if that's the (unlikely) case, so users can at least know that something - // is wrong. Otherwise we'd just silently ignore these types, resulting in runtime failures. - if (!interfaceDefinition.TryGetInterfaceInformationType(interopReferences, out _)) - { - WellKnownInteropExceptions.GeneratedComInterfaceImplementationTypeNotFoundWarning(interfaceDefinition, type).LogOrThrow(args.TreatWarningsAsErrors); - - continue; - } - - // Ensure we can get the '[GuidAttribute]' from the interface. We need this at compile time - // so we can check against some specific IID which might affect how we construct the COM - // interface entries. For instance, we need to check whether 'IMarshal' is implemented. - if (!interfaceDefinition.TryGetGuidAttribute(interopReferences, out Guid iid)) - { - WellKnownInteropExceptions.GeneratedComInterfaceGuidAttributeNotFoundWarning(interfaceDefinition, type).LogOrThrow(args.TreatWarningsAsErrors); - - continue; - } - - // Validate that the current interface isn't trying to implement a reserved interface. - // For instance, it's not allowed to try to explicitly implement 'IUnknown' or 'IInspectable'. - if (WellKnownInterfaceIIDs.ReservedIIDsMap.TryGetValue(iid, out string? interfaceName)) - { - throw WellKnownInteropExceptions.GeneratedComInterfaceReservedGuidError(interfaceDefinition, type, iid, interfaceName); - } - - // Also track all '[GeneratedComInterface]' interfaces too, and filter them later (below) - interfaces.Add(interfaceSignature); - } - } - - // If the user-defined type doesn't implement any Windows Runtime interfaces, it's not considered exposed - if (!hasAnyProjectedWindowsRuntimeInterfaces) - { - continue; - } - - discoveryState.TrackUserDefinedType(type.ToTypeSignature(), interfaces.ToEquatableSet()); + // Track the type (if it's not applicable, we just ignore it) + _ = InteropTypeDiscovery.TryTrackExposedUserDefinedType( + typeDefinition: type, + typeSignature: type.ToTypeSignature(), + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences); } } catch (Exception e) From 329b0ed884959c9b8a97c2e204eb5f92bce2c0b2 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 13:39:22 -0800 Subject: [PATCH 08/18] Refactor generic type tracking into separate file Moved logic for tracking constructed generic types from InteropGenerator.Discover.cs into a new InteropTypeDiscovery.Generics.cs file. This refactoring centralizes and organizes generic type discovery, improving maintainability and separation of concerns. --- .../InteropTypeDiscovery.Generics.cs | 177 ++++++++++++++++++ .../Discovery/InteropTypeDiscovery.cs | 1 + .../Generation/InteropGenerator.Discover.cs | 93 +-------- 3 files changed, 185 insertions(+), 86 deletions(-) create mode 100644 src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs new file mode 100644 index 000000000..75211a7b9 --- /dev/null +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.InteropGenerator.Errors; +using WindowsRuntime.InteropGenerator.Generation; +using WindowsRuntime.InteropGenerator.References; + +namespace WindowsRuntime.InteropGenerator.Discovery; + +/// +internal partial class InteropTypeDiscovery +{ + /// + /// Tries to track a constructed generic 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. + /// Whether the input type was actually tracked, or ignored. + public static bool TryTrackGenericTypeInstance( + GenericInstanceTypeSignature typeSignature, + InteropGeneratorArgs args, + InteropGeneratorDiscoveryState discoveryState, + InteropReferences interopReferences, + ModuleDefinition module) + { + // Ignore types that are not fully resolvable (this likely means a .dll is missing) + if (!typeSignature.IsFullyResolvable(out TypeDefinition? typeDefinition)) + { + // Log a warning the first time we fail to resolve this generic instantiation in this module + if (discoveryState.TrackFailedResolutionType(typeSignature, module)) + { + WellKnownInteropExceptions.GenericTypeSignatureNotResolvedError(typeSignature, module).LogOrThrow(args.TreatWarningsAsErrors); + } + + return false; + } + + // If the current type signature represents a Windows Runtime type, track it + if (typeSignature.IsWindowsRuntimeType(interopReferences)) + { + return TryTrackWindowsRuntimeGenericTypeInstance( + typeDefinition, + typeSignature, + args, + discoveryState, + interopReferences, + module); + } + + // Otherwise, try to track information for some constructed managed type + return TryTrackManagedGenericTypeInstance( + typeDefinition, + typeSignature, + args, + discoveryState, + interopReferences); + } + + /// + /// Tries to track a constructed generic Windows Runtime type. + /// + /// The for the type to analyze. + /// 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. + /// Whether the input type was actually tracked, or ignored. + private static bool TryTrackWindowsRuntimeGenericTypeInstance( + TypeDefinition typeDefinition, + GenericInstanceTypeSignature typeSignature, + InteropGeneratorArgs args, + InteropGeneratorDiscoveryState discoveryState, + InteropReferences interopReferences, + ModuleDefinition module) + { + // Gather all 'KeyValuePair<,>' instances + if (typeSignature.IsValueType && typeSignature.IsConstructedKeyValuePairType(interopReferences)) + { + discoveryState.TrackKeyValuePairType(typeSignature); + + return true; + } + + // Gather all Windows Runtime delegate types. We want to gather all projected delegate types, plus + // any custom-mapped ones (e.g. 'EventHandler' and 'EventHandler'). + // The filtering is already done above, so here we can rely the type will be of one of those kinds. + if (typeDefinition.IsDelegate) + { + discoveryState.TrackGenericDelegateType(typeSignature); + + return true; + } + + // Track all projected Windows Runtime generic interfaces + if (typeDefinition.IsInterface) + { + discoveryState.TrackGenericInterfaceType(typeSignature, interopReferences); + + // We also want to crawl base interfaces + foreach (TypeSignature interfaceSignature in typeSignature.EnumerateAllInterfaces()) + { + // Filter out just constructed generic interfaces, since we only care about those here. + // The non-generic ones are only useful when gathering interfaces for user-defined types. + if (interfaceSignature is not GenericInstanceTypeSignature constructedSignature) + { + continue; + } + + if (!interfaceSignature.IsFullyResolvable(out _)) + { + // Also log a warning the first time we fail to resolve one of the recursively discovered generic + // instantiations from this module. The enumeration also yields back interfaces that couldn't be + // resolved, as that step is performed after yielding. This is done so we can have our own logic + // to log warnings or throw errors from here while we're processing interfaces in this module. + if (discoveryState.TrackFailedResolutionType(interfaceSignature, module)) + { + WellKnownInteropExceptions.GenericTypeSignatureNotResolvedError(interfaceSignature, module).LogOrThrow(args.TreatWarningsAsErrors); + } + + continue; + } + + discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences); + } + + return true; + } + + // This is technically unreachable, assuming the input type is a Windows Runtime type + return false; + } + + /// + /// Tries to track a constructed generic user-defined type. + /// + /// The for the type to analyze. + /// The for the constructed type to analyze. + /// The arguments for this invocation. + /// The discovery state for this invocation. + /// The instance to use. + /// Whether the input type was actually tracked, or ignored. + private static bool TryTrackManagedGenericTypeInstance( + TypeDefinition typeDefinition, + GenericInstanceTypeSignature typeSignature, + InteropGeneratorArgs args, + InteropGeneratorDiscoveryState discoveryState, + InteropReferences interopReferences) + { + // 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 + // those projections require the marshalling code produced when discovering SZ array types. + // So if we see any of these spans where the element type is a Windows Runtime type, we + // manually construct an SZ array type for it and add it to the set of tracked array types. + if (typeSignature.IsValueType && + typeSignature.IsConstructedSpanOrReadOnlySpanType(interopReferences) && + typeSignature.TypeArguments[0].IsWindowsRuntimeType(interopReferences)) + { + discoveryState.TrackSzArrayType(typeSignature.TypeArguments[0].MakeSzArrayType()); + + return false; + } + + // Otherwise, try to track a constructed user-defined type + return TryTrackExposedUserDefinedType( + typeDefinition, + typeSignature, + args, + discoveryState, + interopReferences); + } +} \ 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 b31da198e..b884d3e3b 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs @@ -30,6 +30,7 @@ internal static partial class InteropTypeDiscovery /// The arguments for this invocation. /// The discovery state for this invocation. /// The instance to use. + /// Whether the input type was actually tracked, or ignored. /// /// This method expects to either be non-generic, or /// to have be a fully constructed signature for it. diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index 0a089f8c8..091bea428 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -260,92 +260,13 @@ private static void DiscoverGenericTypeInstantiations( continue; } - // Ignore types that are not fully resolvable (this likely means a .dll is missing) - if (!typeSignature.IsFullyResolvable(out TypeDefinition? typeDefinition)) - { - // Log a warning the first time we fail to resolve this generic instantiation in this module - if (discoveryState.TrackFailedResolutionType(typeSignature, module)) - { - WellKnownInteropExceptions.GenericTypeSignatureNotResolvedError(typeSignature, module).LogOrThrow(args.TreatWarningsAsErrors); - } - - continue; - } - - // 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 - // those projections require the marshalling code produced when discovering SZ array types. - // So if we see any of these spans where the element type is a Windows Runtime type, we - // manually construct an SZ array type for it and add it to the set of tracked array types. - if (typeSignature.IsValueType && - typeSignature.IsConstructedSpanOrReadOnlySpanType(interopReferences) && - typeSignature.TypeArguments[0].IsWindowsRuntimeType(interopReferences)) - { - discoveryState.TrackSzArrayType(typeSignature.TypeArguments[0].MakeSzArrayType()); - - continue; - } - - // Ignore generic instantiations that are not Windows Runtime types. That is, those that - // have a generic type definition that's not a Windows Runtime type, or that have any type - // arguments that are not Windows Runtime types. - if (!typeSignature.IsWindowsRuntimeType(interopReferences)) - { - continue; - } - - // Gather all 'KeyValuePair<,>' instances - if (typeSignature.IsValueType && typeSignature.IsConstructedKeyValuePairType(interopReferences)) - { - discoveryState.TrackKeyValuePairType(typeSignature); - - continue; - } - - // Gather all Windows Runtime delegate types. We want to gather all projected delegate types, plus - // any custom-mapped ones (e.g. 'EventHandler' and 'EventHandler'). - // The filtering is already done above, so here we can rely the type will be of one of those kinds. - if (typeDefinition.IsDelegate) - { - discoveryState.TrackGenericDelegateType(typeSignature); - - continue; - } - - // Track all projected Windows Runtime generic interfaces - if (typeDefinition.IsInterface) - { - discoveryState.TrackGenericInterfaceType(typeSignature, interopReferences); - - // We also want to crawl base interfaces - foreach (TypeSignature interfaceSignature in typeSignature.EnumerateAllInterfaces()) - { - // Filter out just constructed generic interfaces, since we only care about those here. - // The non-generic ones are only useful when gathering interfaces for user-defined types. - if (interfaceSignature is not GenericInstanceTypeSignature constructedSignature) - { - continue; - } - - if (!interfaceSignature.IsFullyResolvable(out _)) - { - // Also log a warning the first time we fail to resolve one of the recursively discovered generic - // instantiations from this module. The enumeration also yields back interfaces that couldn't be - // resolved, as that step is performed after yielding. This is done so we can have our own logic - // to log warnings or throw errors from here while we're processing interfaces in this module. - if (discoveryState.TrackFailedResolutionType(interfaceSignature, module)) - { - WellKnownInteropExceptions.GenericTypeSignatureNotResolvedError(interfaceSignature, module).LogOrThrow(args.TreatWarningsAsErrors); - } - - continue; - } - - discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences); - } - - continue; - } + // Track the constructed generic type (ignore it if not applicable) + _ = InteropTypeDiscovery.TryTrackGenericTypeInstance( + typeSignature: typeSignature, + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); } } catch (Exception e) From 5343d7d9c540f9a8a29c923c279edd14ab30b420 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 14:39:23 -0800 Subject: [PATCH 09/18] Handle newobj and newarr instructions in signature enumeration Extended the EnumerateTypeSignatures method to process 'newobj' and 'newarr' CIL instructions, ensuring that object and array instantiations are included in the signature enumeration. This improves coverage of type signatures encountered in method bodies. --- .../Extensions/ModuleDefinitionExtensions.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/WinRT.Interop.Generator/Extensions/ModuleDefinitionExtensions.cs b/src/WinRT.Interop.Generator/Extensions/ModuleDefinitionExtensions.cs index ac8ffeaac..aeee73acb 100644 --- a/src/WinRT.Interop.Generator/Extensions/ModuleDefinitionExtensions.cs +++ b/src/WinRT.Interop.Generator/Extensions/ModuleDefinitionExtensions.cs @@ -8,6 +8,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata.Tables; using WindowsRuntime.InteropGenerator.Helpers; using WindowsRuntime.InteropGenerator.Visitors; @@ -230,6 +231,58 @@ static IEnumerable EnumerateTypeSignatures( yield return result; } } + + IReadOnlyList instructions = specification.Method!.Resolve()?.CilMethodBody?.Instructions ?? (IReadOnlyList)[]; + + // Go through instruction to look for new objects + foreach (CilInstruction instruction in instructions) + { + // We only care for 'newobj' instructions + if (instruction.OpCode != CilOpCodes.Newobj) + { + continue; + } + + // Check that we can retrieve the target object type + if (instruction.Operand is not IMethodDefOrRef { DeclaringType: ITypeDefOrRef objectType }) + { + continue; + } + + // Instantiate the object type and enumerate all signatures + foreach (TResult result in EnumerateTypeSignatures( + objectType.ToTypeSignature().InstantiateGenericTypes(genericContext), + results, + visitor)) + { + yield return result; + } + } + + // Go through instruction to look for new arrays + foreach (CilInstruction instruction in instructions) + { + // We only care for 'newarr' instructions + if (instruction.OpCode != CilOpCodes.Newarr) + { + continue; + } + + // Check that we can retrieve the target object type + if (instruction.Operand is not ITypeDefOrRef arrayType) + { + continue; + } + + // Instantiate the object type and enumerate all signatures + foreach (TResult result in EnumerateTypeSignatures( + arrayType.ToTypeSignature().InstantiateGenericTypes(genericContext), + results, + visitor)) + { + yield return result; + } + } } } From 9dac944d68d64f77b3c0a53196a48f3fcd57a245 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 14:45:00 -0800 Subject: [PATCH 10/18] Refactor type tracking logic into helper methods Moved SZ array and type hierarchy tracking logic from InteropGenerator.Discover.cs into dedicated helper methods in InteropTypeDiscovery. This improves code reuse and maintainability by centralizing the logic for type analysis and tracking. --- .../InteropTypeDiscovery.Generics.cs | 40 ++++++++++++++ .../Discovery/InteropTypeDiscovery.cs | 43 +++++++++++++++ .../Generation/InteropGenerator.Discover.cs | 53 +++---------------- 3 files changed, 91 insertions(+), 45 deletions(-) diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs index 75211a7b9..07b460218 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs @@ -61,6 +61,46 @@ public static bool TryTrackGenericTypeInstance( interopReferences); } + /// + /// Tries to track an SZ array type. + /// + /// The for the SZ array type to analyze. + /// The arguments for this invocation. + /// The discovery state for this invocation. + /// The instance to use. + /// The module currently being analyzed. + /// Whether the input type was actually tracked, or ignored. + public static bool TryTrackSzArrayType( + SzArrayTypeSignature typeSignature, + InteropGeneratorArgs args, + InteropGeneratorDiscoveryState discoveryState, + InteropReferences interopReferences, + ModuleDefinition module) + { + // Ignore types that are not fully resolvable (this likely means a .dll is missing) + if (!typeSignature.IsFullyResolvable(out _)) + { + // Log a warning the first time we fail to resolve this SZ array in this module + if (discoveryState.TrackFailedResolutionType(typeSignature, module)) + { + WellKnownInteropExceptions.SzArrayTypeSignatureNotResolvedError(typeSignature, module).LogOrThrow(args.TreatWarningsAsErrors); + } + + return false; + } + + // Ignore array types that are not Windows Runtime types + if (!typeSignature.IsWindowsRuntimeType(interopReferences)) + { + return false; + } + + // Track all SZ array types, as we'll need to emit marshalling code for them + discoveryState.TrackSzArrayType(typeSignature); + + return true; + } + /// /// Tries to track a constructed generic Windows Runtime type. /// diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs index b884d3e3b..5a3d0dcb8 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs @@ -22,6 +22,49 @@ internal static partial class InteropTypeDiscovery [ThreadStatic] private static TypeSignatureEquatableSet.Builder? TypeSignatures; + /// + /// Tries to track a given composable Windows Runtime type. + /// + /// The for the type to analyze. + /// The arguments for this invocation. + /// The discovery state for this invocation. + /// Whether the input type was actually tracked, or ignored. + public static bool TryTrackTypeHierarchyType( + TypeDefinition typeDefinition, + InteropGeneratorArgs args, + InteropGeneratorDiscoveryState discoveryState) + { + // We only care about projected Windows Runtime classes + if (!typeDefinition.IsProjectedWindowsRuntimeClassType) + { + return false; + } + + // Ignore types that don't have another base class + if (!typeDefinition.HasBaseType(out ITypeDefOrRef? baseType)) + { + return false; + } + + // We need to resolve the base type to be able to look up attributes on it + if (!baseType.IsFullyResolvable(out _)) + { + WellKnownInteropExceptions.WindowsRuntimeClassTypeNotResolvedWarning(baseType, typeDefinition).LogOrThrow(args.TreatWarningsAsErrors); + + return false; + } + + // If the base type is also a projected Windows Runtime type, track it + if (baseType.IsProjectedWindowsRuntimeType) + { + discoveryState.TrackTypeHierarchyEntry(typeDefinition.FullName, baseType.FullName); + + return true; + } + + return false; + } + /// /// Tries to track an exposed user-defined type. /// diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index 091bea428..0d6059f6b 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -156,31 +156,7 @@ private static void DiscoverTypeHierarchyTypes( { args.Token.ThrowIfCancellationRequested(); - // We only care about projected Windows Runtime classes - if (!type.IsProjectedWindowsRuntimeClassType) - { - continue; - } - - // Ignore types that don't have another base class - if (!type.HasBaseType(out ITypeDefOrRef? baseType)) - { - continue; - } - - // We need to resolve the base type to be able to look up attributes on it - if (!baseType.IsFullyResolvable(out TypeDefinition? baseDefinition)) - { - WellKnownInteropExceptions.WindowsRuntimeClassTypeNotResolvedWarning(baseType, type).LogOrThrow(args.TreatWarningsAsErrors); - - continue; - } - - // If the base type is also a projected Windows Runtime type, track it - if (baseType.IsProjectedWindowsRuntimeType) - { - discoveryState.TrackTypeHierarchyEntry(type.FullName, baseType.FullName); - } + _ = InteropTypeDiscovery.TryTrackTypeHierarchyType(type, args, discoveryState); } } catch (Exception e) @@ -301,26 +277,13 @@ private static void DiscoverSzArrayTypes( continue; } - // Ignore types that are not fully resolvable (this likely means a .dll is missing) - if (!typeSignature.IsFullyResolvable(out _)) - { - // Log a warning the first time we fail to resolve this SZ array in this module - if (discoveryState.TrackFailedResolutionType(typeSignature, module)) - { - WellKnownInteropExceptions.SzArrayTypeSignatureNotResolvedError(typeSignature, module).LogOrThrow(args.TreatWarningsAsErrors); - } - - continue; - } - - // Ignore array types that are not Windows Runtime types - if (!typeSignature.IsWindowsRuntimeType(interopReferences)) - { - continue; - } - - // Track all SZ array types, as we'll need to emit marshalling code for them - discoveryState.TrackSzArrayType(typeSignature); + // Track the SZ array type + _ = InteropTypeDiscovery.TryTrackSzArrayType( + typeSignature: typeSignature, + args: args, + discoveryState: discoveryState, + interopReferences: interopReferences, + module: module); } } catch (Exception e) From d9a38cbded6b129b3949629218492549490272e1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 14:47:41 -0800 Subject: [PATCH 11/18] Refactor tracking methods to return void instead of bool Updated InteropTypeDiscovery tracking methods to return void instead of bool, as their return values were not used. Adjusted all call sites and removed related comments and unreachable code for clarity. --- .../InteropTypeDiscovery.Generics.cs | 53 ++++++++----------- .../Discovery/InteropTypeDiscovery.cs | 24 +++------ .../Generation/InteropGenerator.Discover.cs | 14 ++--- 3 files changed, 37 insertions(+), 54 deletions(-) diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs index 07b460218..ee1774c15 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs @@ -20,8 +20,7 @@ internal partial class InteropTypeDiscovery /// The discovery state for this invocation. /// The instance to use. /// The module currently being analyzed. - /// Whether the input type was actually tracked, or ignored. - public static bool TryTrackGenericTypeInstance( + public static void TryTrackGenericTypeInstance( GenericInstanceTypeSignature typeSignature, InteropGeneratorArgs args, InteropGeneratorDiscoveryState discoveryState, @@ -37,13 +36,13 @@ public static bool TryTrackGenericTypeInstance( WellKnownInteropExceptions.GenericTypeSignatureNotResolvedError(typeSignature, module).LogOrThrow(args.TreatWarningsAsErrors); } - return false; + return; } // If the current type signature represents a Windows Runtime type, track it if (typeSignature.IsWindowsRuntimeType(interopReferences)) { - return TryTrackWindowsRuntimeGenericTypeInstance( + TryTrackWindowsRuntimeGenericTypeInstance( typeDefinition, typeSignature, args, @@ -51,14 +50,16 @@ public static bool TryTrackGenericTypeInstance( interopReferences, module); } - - // Otherwise, try to track information for some constructed managed type - return TryTrackManagedGenericTypeInstance( - typeDefinition, - typeSignature, - args, - discoveryState, - interopReferences); + else + { + // Otherwise, try to track information for some constructed managed type + TryTrackManagedGenericTypeInstance( + typeDefinition, + typeSignature, + args, + discoveryState, + interopReferences); + } } /// @@ -69,8 +70,7 @@ public static bool TryTrackGenericTypeInstance( /// The discovery state for this invocation. /// The instance to use. /// The module currently being analyzed. - /// Whether the input type was actually tracked, or ignored. - public static bool TryTrackSzArrayType( + public static void TryTrackSzArrayType( SzArrayTypeSignature typeSignature, InteropGeneratorArgs args, InteropGeneratorDiscoveryState discoveryState, @@ -86,19 +86,17 @@ public static bool TryTrackSzArrayType( WellKnownInteropExceptions.SzArrayTypeSignatureNotResolvedError(typeSignature, module).LogOrThrow(args.TreatWarningsAsErrors); } - return false; + return; } // Ignore array types that are not Windows Runtime types if (!typeSignature.IsWindowsRuntimeType(interopReferences)) { - return false; + return; } // Track all SZ array types, as we'll need to emit marshalling code for them discoveryState.TrackSzArrayType(typeSignature); - - return true; } /// @@ -110,8 +108,7 @@ public static bool TryTrackSzArrayType( /// The discovery state for this invocation. /// The instance to use. /// The module currently being analyzed. - /// Whether the input type was actually tracked, or ignored. - private static bool TryTrackWindowsRuntimeGenericTypeInstance( + private static void TryTrackWindowsRuntimeGenericTypeInstance( TypeDefinition typeDefinition, GenericInstanceTypeSignature typeSignature, InteropGeneratorArgs args, @@ -124,7 +121,7 @@ private static bool TryTrackWindowsRuntimeGenericTypeInstance( { discoveryState.TrackKeyValuePairType(typeSignature); - return true; + return; } // Gather all Windows Runtime delegate types. We want to gather all projected delegate types, plus @@ -134,7 +131,7 @@ private static bool TryTrackWindowsRuntimeGenericTypeInstance( { discoveryState.TrackGenericDelegateType(typeSignature); - return true; + return; } // Track all projected Windows Runtime generic interfaces @@ -168,12 +165,7 @@ private static bool TryTrackWindowsRuntimeGenericTypeInstance( discoveryState.TrackGenericInterfaceType(constructedSignature, interopReferences); } - - return true; } - - // This is technically unreachable, assuming the input type is a Windows Runtime type - return false; } /// @@ -184,8 +176,7 @@ private static bool TryTrackWindowsRuntimeGenericTypeInstance( /// The arguments for this invocation. /// The discovery state for this invocation. /// The instance to use. - /// Whether the input type was actually tracked, or ignored. - private static bool TryTrackManagedGenericTypeInstance( + private static void TryTrackManagedGenericTypeInstance( TypeDefinition typeDefinition, GenericInstanceTypeSignature typeSignature, InteropGeneratorArgs args, @@ -203,11 +194,11 @@ private static bool TryTrackManagedGenericTypeInstance( { discoveryState.TrackSzArrayType(typeSignature.TypeArguments[0].MakeSzArrayType()); - return false; + return; } // Otherwise, try to track a constructed user-defined type - return TryTrackExposedUserDefinedType( + TryTrackExposedUserDefinedType( typeDefinition, typeSignature, args, diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs index 5a3d0dcb8..13005bd03 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs @@ -28,8 +28,7 @@ internal static partial class InteropTypeDiscovery /// The for the type to analyze. /// The arguments for this invocation. /// The discovery state for this invocation. - /// Whether the input type was actually tracked, or ignored. - public static bool TryTrackTypeHierarchyType( + public static void TryTrackTypeHierarchyType( TypeDefinition typeDefinition, InteropGeneratorArgs args, InteropGeneratorDiscoveryState discoveryState) @@ -37,13 +36,13 @@ public static bool TryTrackTypeHierarchyType( // We only care about projected Windows Runtime classes if (!typeDefinition.IsProjectedWindowsRuntimeClassType) { - return false; + return; } // Ignore types that don't have another base class if (!typeDefinition.HasBaseType(out ITypeDefOrRef? baseType)) { - return false; + return; } // We need to resolve the base type to be able to look up attributes on it @@ -51,18 +50,14 @@ public static bool TryTrackTypeHierarchyType( { WellKnownInteropExceptions.WindowsRuntimeClassTypeNotResolvedWarning(baseType, typeDefinition).LogOrThrow(args.TreatWarningsAsErrors); - return false; + return; } // If the base type is also a projected Windows Runtime type, track it if (baseType.IsProjectedWindowsRuntimeType) { discoveryState.TrackTypeHierarchyEntry(typeDefinition.FullName, baseType.FullName); - - return true; } - - return false; } /// @@ -73,12 +68,11 @@ public static bool TryTrackTypeHierarchyType( /// The arguments for this invocation. /// The discovery state for this invocation. /// The instance to use. - /// Whether the input type was actually tracked, or ignored. /// /// This method expects to either be non-generic, or /// to have be a fully constructed signature for it. /// - public static bool TryTrackExposedUserDefinedType( + public static void TryTrackExposedUserDefinedType( TypeDefinition typeDefinition, TypeSignature typeSignature, InteropGeneratorArgs args, @@ -88,7 +82,7 @@ public static bool TryTrackExposedUserDefinedType( // We can skip all projected Windows Runtime types early, as they don't need CCW support if (typeDefinition.IsProjectedWindowsRuntimeType) { - return false; + return; } // We'll need to look up attributes and enumerate interfaces across the entire type @@ -97,13 +91,13 @@ public static bool TryTrackExposedUserDefinedType( { WellKnownInteropExceptions.UserDefinedTypeNotFullyResolvedWarning(failedResolutionBaseType, typeDefinition).LogOrThrow(args.TreatWarningsAsErrors); - return false; + return; } // We only want to process non-generic user-defined types that are potentially exposed to Windows Runtime if (!typeDefinition.IsPossiblyWindowsRuntimeExposedType || typeDefinition.IsWindowsRuntimeManagedOnlyType(interopReferences)) { - return false; + return; } // Reuse the thread-local builder to track all implemented interfaces for the current type @@ -194,7 +188,5 @@ public static bool TryTrackExposedUserDefinedType( { discoveryState.TrackUserDefinedType(typeSignature, interfaces.ToEquatableSet()); } - - return true; } } \ No newline at end of file diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index 0d6059f6b..7a58c4ed2 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -156,7 +156,7 @@ private static void DiscoverTypeHierarchyTypes( { args.Token.ThrowIfCancellationRequested(); - _ = InteropTypeDiscovery.TryTrackTypeHierarchyType(type, args, discoveryState); + InteropTypeDiscovery.TryTrackTypeHierarchyType(type, args, discoveryState); } } catch (Exception e) @@ -195,8 +195,8 @@ private static void DiscoverExposedUserDefinedTypes( continue; } - // Track the type (if it's not applicable, we just ignore it) - _ = InteropTypeDiscovery.TryTrackExposedUserDefinedType( + // Track the type (if it's not applicable, it will be a no-op) + InteropTypeDiscovery.TryTrackExposedUserDefinedType( typeDefinition: type, typeSignature: type.ToTypeSignature(), args: args, @@ -236,8 +236,8 @@ private static void DiscoverGenericTypeInstantiations( continue; } - // Track the constructed generic type (ignore it if not applicable) - _ = InteropTypeDiscovery.TryTrackGenericTypeInstance( + // Track the constructed generic type (if it's not applicable, it will be a no-op) + InteropTypeDiscovery.TryTrackGenericTypeInstance( typeSignature: typeSignature, args: args, discoveryState: discoveryState, @@ -277,8 +277,8 @@ private static void DiscoverSzArrayTypes( continue; } - // Track the SZ array type - _ = InteropTypeDiscovery.TryTrackSzArrayType( + // Track the SZ array type (if it's not applicable, it will be a no-op) + InteropTypeDiscovery.TryTrackSzArrayType( typeSignature: typeSignature, args: args, discoveryState: discoveryState, From 1f0c48f70117a8ad495fba711aa18f9c824c884d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 15:32:18 -0800 Subject: [PATCH 12/18] Filter unconstructed generic types during discovery Added checks to skip unconstructed generic type definitions during interop type discovery and generation. This ensures only constructed generic types are considered for marshalling code, improving accuracy and avoiding unnecessary processing of generic definitions. --- .../Discovery/InteropTypeDiscovery.Generics.cs | 15 +++++++++++++++ .../Discovery/InteropTypeDiscovery.cs | 7 +++++++ .../Generation/InteropGenerator.Discover.cs | 8 -------- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs index ee1774c15..ac66c8781 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs @@ -6,6 +6,7 @@ using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Generation; using WindowsRuntime.InteropGenerator.References; +using WindowsRuntime.InteropGenerator.Visitors; namespace WindowsRuntime.InteropGenerator.Discovery; @@ -27,6 +28,13 @@ public static void TryTrackGenericTypeInstance( InteropReferences interopReferences, ModuleDefinition module) { + // Filter all constructed generic type signatures we have. We don't care about generic type + // definitions (eg. 'TypedEventHandler`1') for the purposes of marshalling code. + if (!typeSignature.AcceptVisitor(IsConstructedGenericTypeVisitor.Instance)) + { + return; + } + // Ignore types that are not fully resolvable (this likely means a .dll is missing) if (!typeSignature.IsFullyResolvable(out TypeDefinition? typeDefinition)) { @@ -77,6 +85,13 @@ public static void TryTrackSzArrayType( InteropReferences interopReferences, ModuleDefinition module) { + // Filter all constructed generic type signatures we have. We don't care about + // generic type definitions (eg. '!0[]') for the purposes of marshalling code. + if (!typeSignature.AcceptVisitor(IsConstructedGenericTypeVisitor.Instance)) + { + return; + } + // Ignore types that are not fully resolvable (this likely means a .dll is missing) if (!typeSignature.IsFullyResolvable(out _)) { diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs index 13005bd03..503393287 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs @@ -79,6 +79,13 @@ public static void TryTrackExposedUserDefinedType( InteropGeneratorDiscoveryState discoveryState, InteropReferences interopReferences) { + // Ignore all type definitions with generic parameters where we don't have constructed + // generic type signature. We can track these separately when we see them as instantiated. + if (typeDefinition.HasGenericParameters && typeSignature is not GenericInstanceTypeSignature) + { + return; + } + // We can skip all projected Windows Runtime types early, as they don't need CCW support if (typeDefinition.IsProjectedWindowsRuntimeType) { diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index 7a58c4ed2..cd13d6761 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -187,14 +187,6 @@ private static void DiscoverExposedUserDefinedTypes( { args.Token.ThrowIfCancellationRequested(); - // Ignore all type definitions with generic parameters, because they would be - // unconstructed (by definition). We'll process instantiations that we can see - // separately in the discovery phase, same as we do for constructed interfaces. - if (type.HasGenericParameters) - { - continue; - } - // Track the type (if it's not applicable, it will be a no-op) InteropTypeDiscovery.TryTrackExposedUserDefinedType( typeDefinition: type, From dab3ee1b46602144519a0792cd3bb800087cd933 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 15:03:27 -0800 Subject: [PATCH 13/18] Add WindowsRuntimeManagedOnlyType attribute to class Applied the [WindowsRuntimeManagedOnlyType] attribute to the WindowsRuntimeObjectReference class to indicate it is intended for managed use only. --- .../ObjectReference/WindowsRuntimeObjectReference.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/WinRT.Runtime2/InteropServices/ObjectReference/WindowsRuntimeObjectReference.cs b/src/WinRT.Runtime2/InteropServices/ObjectReference/WindowsRuntimeObjectReference.cs index 26bb13096..ee06ae965 100644 --- a/src/WinRT.Runtime2/InteropServices/ObjectReference/WindowsRuntimeObjectReference.cs +++ b/src/WinRT.Runtime2/InteropServices/ObjectReference/WindowsRuntimeObjectReference.cs @@ -10,6 +10,7 @@ namespace WindowsRuntime.InteropServices; /// /// A managed, low-level wrapper for a native Windows Runtime object. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] From 9916a289c6df49235ccd85039478b73b492408f6 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 15:46:31 -0800 Subject: [PATCH 14/18] Add TypeExclusions helper for interop API filtering Introduces a TypeExclusions helper class to centralize logic for excluding specific types, such as Task, from the interop API surface. Also adds a Task1 reference to InteropReferences to support this exclusion logic. --- .../Helpers/TypeExclusions.cs | 42 +++++++++++++++++++ .../References/InteropReferences.cs | 5 +++ 2 files changed, 47 insertions(+) create mode 100644 src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs diff --git a/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs b/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs new file mode 100644 index 000000000..f95617453 --- /dev/null +++ b/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using WindowsRuntime.InteropGenerator.References; + +namespace WindowsRuntime.InteropGenerator.Helpers; + +/// +/// Contains logic to handle special-cases types to exclude from the interop API surface. +/// +internal static class TypeExclusions +{ + /// + /// Checks whether a given type should be excluded from the interop API surface. + /// + /// The type to check. + /// The instance to use. + /// Whether should be excluded from the interop API surface. + public static bool IsExcluded(ITypeDescriptor type, InteropReferences interopReferences) + { + // If we have a constructed generic type, extract the generic type definition + // and use that for checking. We don't have exclusion logic for type arguments. + if (type is GenericInstanceTypeSignature typeSignature) + { + return IsExcluded(typeSignature.GenericType, interopReferences); + } + + // Check if the input type matches any of our exclusions + foreach (TypeReference excludedType in (ReadOnlySpan)[interopReferences.Task1]) + { + if (SignatureComparer.IgnoreVersion.Equals(type, excludedType)) + { + return true; + } + } + + return false; + } +} diff --git a/src/WinRT.Interop.Generator/References/InteropReferences.cs b/src/WinRT.Interop.Generator/References/InteropReferences.cs index 348103964..36bc0b02b 100644 --- a/src/WinRT.Interop.Generator/References/InteropReferences.cs +++ b/src/WinRT.Interop.Generator/References/InteropReferences.cs @@ -193,6 +193,11 @@ public InteropReferences( /// public GenericInstanceTypeSignature ReadOnlySpanInt32 => field ??= ReadOnlySpan1.MakeGenericValueType(_corLibTypeFactory.Int32); + /// + /// Gets the for . + /// + public TypeReference Task1 => field ??= _corLibTypeFactory.CorLibScope.CreateTypeReference("System.Threading.Tasks"u8, "Task`1"u8); + /// /// Gets the for . /// From 24723e4976042fed4c9cc81ddf883b6acf582b2c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 15:51:26 -0800 Subject: [PATCH 15/18] Add type exclusion checks to interop type discovery Introduced checks in InteropTypeDiscovery to skip types explicitly excluded via TypeExclusions, including support for SZ arrays. This prevents processing of types that should not be considered for interop marshalling, improving filtering and correctness. --- .../Discovery/InteropTypeDiscovery.Generics.cs | 13 +++++++++++++ .../Discovery/InteropTypeDiscovery.cs | 7 +++++++ .../Helpers/TypeExclusions.cs | 6 ++++++ 3 files changed, 26 insertions(+) diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs index ac66c8781..593d5640a 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.Generics.cs @@ -5,6 +5,7 @@ using AsmResolver.DotNet.Signatures; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Generation; +using WindowsRuntime.InteropGenerator.Helpers; using WindowsRuntime.InteropGenerator.References; using WindowsRuntime.InteropGenerator.Visitors; @@ -28,6 +29,12 @@ public static void TryTrackGenericTypeInstance( InteropReferences interopReferences, ModuleDefinition module) { + // Ignore types that should explicitly be excluded + if (TypeExclusions.IsExcluded(typeSignature, interopReferences)) + { + return; + } + // Filter all constructed generic type signatures we have. We don't care about generic type // definitions (eg. 'TypedEventHandler`1') for the purposes of marshalling code. if (!typeSignature.AcceptVisitor(IsConstructedGenericTypeVisitor.Instance)) @@ -85,6 +92,12 @@ public static void TryTrackSzArrayType( InteropReferences interopReferences, ModuleDefinition module) { + // Ignore types that should explicitly be excluded + if (TypeExclusions.IsExcluded(typeSignature, interopReferences)) + { + return; + } + // Filter all constructed generic type signatures we have. We don't care about // generic type definitions (eg. '!0[]') for the purposes of marshalling code. if (!typeSignature.AcceptVisitor(IsConstructedGenericTypeVisitor.Instance)) diff --git a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs index 503393287..3a6f8b9a9 100644 --- a/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs +++ b/src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs @@ -6,6 +6,7 @@ using AsmResolver.DotNet.Signatures; using WindowsRuntime.InteropGenerator.Errors; using WindowsRuntime.InteropGenerator.Generation; +using WindowsRuntime.InteropGenerator.Helpers; using WindowsRuntime.InteropGenerator.Models; using WindowsRuntime.InteropGenerator.References; @@ -79,6 +80,12 @@ public static void TryTrackExposedUserDefinedType( InteropGeneratorDiscoveryState discoveryState, InteropReferences interopReferences) { + // Ignore types that should explicitly be excluded + if (TypeExclusions.IsExcluded(typeDefinition, interopReferences)) + { + return; + } + // Ignore all type definitions with generic parameters where we don't have constructed // generic type signature. We can track these separately when we see them as instantiated. if (typeDefinition.HasGenericParameters && typeSignature is not GenericInstanceTypeSignature) diff --git a/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs b/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs index f95617453..61529f5bd 100644 --- a/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs +++ b/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs @@ -28,6 +28,12 @@ public static bool IsExcluded(ITypeDescriptor type, InteropReferences interopRef return IsExcluded(typeSignature.GenericType, interopReferences); } + // Also handle SZ arrays and check their element type + if (type is SzArrayTypeSignature arraySignature) + { + return IsExcluded(arraySignature.BaseType, interopReferences); + } + // Check if the input type matches any of our exclusions foreach (TypeReference excludedType in (ReadOnlySpan)[interopReferences.Task1]) { From a9e8df9241447f18d54b1f4a20087509ae3cae12 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 16:03:17 -0800 Subject: [PATCH 16/18] Fix typo Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs b/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs index 61529f5bd..63c74ff0e 100644 --- a/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs +++ b/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs @@ -9,7 +9,7 @@ namespace WindowsRuntime.InteropGenerator.Helpers; /// -/// Contains logic to handle special-cases types to exclude from the interop API surface. +/// Contains logic to handle special-case types to exclude from the interop API surface. /// internal static class TypeExclusions { From 656532796db98ee31e2996987b6f82a1afdb74dd Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 16 Dec 2025 09:50:41 -0800 Subject: [PATCH 17/18] Add WindowsRuntimeManagedOnlyType attribute to runtime types Applied the [WindowsRuntimeManagedOnlyType] attribute to various Windows Runtime collection and async types in the WinRT.Runtime2 project. This change clarifies that these types are managed-only implementations, improving code clarity and maintainability. --- src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncAction.cs | 1 + .../WindowsRuntimeAsyncActionWithProgress{TProgress}.cs | 1 + ...ndowsRuntimeAsyncOperationWithProgress{TResult, TProgress}.cs | 1 + .../AsyncInfo/WindowsRuntimeAsyncOperation{TResult}.cs | 1 + src/WinRT.Runtime2/Bindables/WindowsRuntimeEnumerable.cs | 1 + src/WinRT.Runtime2/Bindables/WindowsRuntimeIterator.cs | 1 + src/WinRT.Runtime2/Bindables/WindowsRuntimeList.cs | 1 + .../Collections/WindowsRuntimeDictionary{TKey, TValue}.cs | 1 + src/WinRT.Runtime2/Collections/WindowsRuntimeEnumerable{T}.cs | 1 + src/WinRT.Runtime2/Collections/WindowsRuntimeEnumerator{T}.cs | 1 + src/WinRT.Runtime2/Collections/WindowsRuntimeList{T}.cs | 1 + .../Collections/WindowsRuntimeMapChangedEventArgs{TKey}.cs | 1 + .../Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs | 1 + .../Collections/WindowsRuntimeObservableVector{T}.cs | 1 + .../WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs | 1 + src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyList{T}.cs | 1 + 16 files changed, 16 insertions(+) diff --git a/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncAction.cs b/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncAction.cs index 03cdcf9c8..b6720c55f 100644 --- a/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncAction.cs +++ b/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncAction.cs @@ -14,6 +14,7 @@ namespace WindowsRuntime; /// The implementation of a native object for . /// /// +[WindowsRuntimeManagedOnlyType] internal sealed class WindowsRuntimeAsyncAction : WindowsRuntimeObject, IAsyncAction, IWindowsRuntimeInterface, diff --git a/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncActionWithProgress{TProgress}.cs b/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncActionWithProgress{TProgress}.cs index 31213b8ba..7f192a974 100644 --- a/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncActionWithProgress{TProgress}.cs +++ b/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncActionWithProgress{TProgress}.cs @@ -17,6 +17,7 @@ namespace WindowsRuntime; /// The type of progress information. /// The implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncOperationWithProgress{TResult, TProgress}.cs b/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncOperationWithProgress{TResult, TProgress}.cs index a9d1dd212..a24863977 100644 --- a/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncOperationWithProgress{TResult, TProgress}.cs +++ b/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncOperationWithProgress{TResult, TProgress}.cs @@ -18,6 +18,7 @@ namespace WindowsRuntime; /// The type of progress information. /// The implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncOperation{TResult}.cs b/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncOperation{TResult}.cs index 53113048a..86bdd28dc 100644 --- a/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncOperation{TResult}.cs +++ b/src/WinRT.Runtime2/AsyncInfo/WindowsRuntimeAsyncOperation{TResult}.cs @@ -17,6 +17,7 @@ namespace WindowsRuntime; /// The result type. /// The implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/Bindables/WindowsRuntimeEnumerable.cs b/src/WinRT.Runtime2/Bindables/WindowsRuntimeEnumerable.cs index ceae5ea36..c7f81f8e8 100644 --- a/src/WinRT.Runtime2/Bindables/WindowsRuntimeEnumerable.cs +++ b/src/WinRT.Runtime2/Bindables/WindowsRuntimeEnumerable.cs @@ -12,6 +12,7 @@ namespace WindowsRuntime; /// The implementation of all projected Windows Runtime types. /// /// +[WindowsRuntimeManagedOnlyType] internal sealed class WindowsRuntimeEnumerable : WindowsRuntimeObject, IEnumerable, IWindowsRuntimeInterface { /// diff --git a/src/WinRT.Runtime2/Bindables/WindowsRuntimeIterator.cs b/src/WinRT.Runtime2/Bindables/WindowsRuntimeIterator.cs index e2e6c0e87..09ab26209 100644 --- a/src/WinRT.Runtime2/Bindables/WindowsRuntimeIterator.cs +++ b/src/WinRT.Runtime2/Bindables/WindowsRuntimeIterator.cs @@ -15,6 +15,7 @@ namespace WindowsRuntime; /// The implementation of all projected Windows Runtime types. /// /// +[WindowsRuntimeManagedOnlyType] internal sealed unsafe class WindowsRuntimeIterator : WindowsRuntimeObject, IEnumerator, IWindowsRuntimeInterface { /// diff --git a/src/WinRT.Runtime2/Bindables/WindowsRuntimeList.cs b/src/WinRT.Runtime2/Bindables/WindowsRuntimeList.cs index 640eb5ee4..47b275fd7 100644 --- a/src/WinRT.Runtime2/Bindables/WindowsRuntimeList.cs +++ b/src/WinRT.Runtime2/Bindables/WindowsRuntimeList.cs @@ -14,6 +14,7 @@ namespace WindowsRuntime; /// The implementation of all projected Windows Runtime types. /// /// +[WindowsRuntimeManagedOnlyType] internal sealed class WindowsRuntimeList : WindowsRuntimeObject, IList, IWindowsRuntimeInterface, diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs index 17f4d7cf1..1f6a55a3b 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeDictionary{TKey, TValue}.cs @@ -21,6 +21,7 @@ namespace WindowsRuntime; /// The Windows.Foundation.Collections.IIterable<T> implementation type. /// The Windows.Foundation.Collections.IMap<K, V> implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeEnumerable{T}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeEnumerable{T}.cs index 0f83daeac..6b7159e39 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeEnumerable{T}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeEnumerable{T}.cs @@ -17,6 +17,7 @@ namespace WindowsRuntime; /// The type of objects to enumerate. /// The Windows.Foundation.Collections.IIterable<T> implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeEnumerator{T}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeEnumerator{T}.cs index dfddca617..637d08e82 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeEnumerator{T}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeEnumerator{T}.cs @@ -18,6 +18,7 @@ namespace WindowsRuntime; /// The type of objects to enumerate. /// The implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeList{T}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeList{T}.cs index 67d624505..87a49a9ef 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeList{T}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeList{T}.cs @@ -21,6 +21,7 @@ namespace WindowsRuntime; /// The Windows.Foundation.Collections.IIterable<T> implementation type. /// The Windows.Foundation.Collections.IVector<T> implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeMapChangedEventArgs{TKey}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeMapChangedEventArgs{TKey}.cs index 09eccc3f3..fa97363bd 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeMapChangedEventArgs{TKey}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeMapChangedEventArgs{TKey}.cs @@ -14,6 +14,7 @@ namespace WindowsRuntime; /// The type of keys in the map. /// The Windows.Foundation.Collections.IMapChangedEventArgs<K> implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs index 79c5570ef..efa2a4a95 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableMap{TKey, TValue}.cs @@ -26,6 +26,7 @@ namespace WindowsRuntime; /// The Windows.Foundation.Collections.IMap<K, V> implementation type. /// The Windows.Foundation.Collections.IObservableMap<K, V> implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableVector{T}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableVector{T}.cs index c2da94849..a1df93827 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableVector{T}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeObservableVector{T}.cs @@ -24,6 +24,7 @@ namespace WindowsRuntime; /// The Windows.Foundation.Collections.IVector<T> implementation type. /// The Windows.Foundation.Collections.IObservableVector<T> implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs index e2e28038e..df74e42c4 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyDictionary{TKey, TValue}.cs @@ -21,6 +21,7 @@ namespace WindowsRuntime; /// The Windows.Foundation.Collections.IIterable<T> implementation type. /// The Windows.Foundation.Collections.IMapView<K, V> implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] diff --git a/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyList{T}.cs b/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyList{T}.cs index 3c1ebf79a..bf171a5de 100644 --- a/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyList{T}.cs +++ b/src/WinRT.Runtime2/Collections/WindowsRuntimeReadOnlyList{T}.cs @@ -19,6 +19,7 @@ namespace WindowsRuntime; /// The Windows.Foundation.Collections.IIterable<T> implementation type. /// The Windows.Foundation.Collections.IVectorView<T> implementation type. /// +[WindowsRuntimeManagedOnlyType] [Obsolete(WindowsRuntimeConstants.PrivateImplementationDetailObsoleteMessage, DiagnosticId = WindowsRuntimeConstants.PrivateImplementationDetailObsoleteDiagnosticId, UrlFormat = WindowsRuntimeConstants.CsWinRTDiagnosticsUrlFormat)] From 80784bfda52951a318e67928150724ef6b9e8bce Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 9 Jan 2026 14:32:56 -0800 Subject: [PATCH 18/18] Refactor type exclusion logic for marshalling Replaces manual iteration over excluded types with a stack-allocated ReadOnlySpan and uses Contains for cleaner and more efficient exclusion checks. This improves readability and performance by leveraging modern C# features. --- .../Helpers/TypeExclusions.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs b/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs index 63c74ff0e..74b5ab799 100644 --- a/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs +++ b/src/WinRT.Interop.Generator/Helpers/TypeExclusions.cs @@ -34,15 +34,17 @@ public static bool IsExcluded(ITypeDescriptor type, InteropReferences interopRef return IsExcluded(arraySignature.BaseType, interopReferences); } - // Check if the input type matches any of our exclusions - foreach (TypeReference excludedType in (ReadOnlySpan)[interopReferences.Task1]) - { - if (SignatureComparer.IgnoreVersion.Equals(type, excludedType)) - { - return true; - } - } + // This is the full set of types we want to always exclude from marshalling. + // We can't put this in a global variable as we need the 'InteropReferences' + // instance to actually retrieve the type references to enumerate. However + // by just using a 'ReadOnlySpan' here, the full list is stack-allocated. + ReadOnlySpan excludedTypes = + [ + interopReferences.Task1, + interopReferences.ConditionalWeakTable2 + ]; - return false; + // Check if the input type matches any of our exclusions + return excludedTypes.Contains(type, SignatureComparer.IgnoreVersion); } }