From fe460463da629eefeea1faf680d0a417d993b2df Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 13 Dec 2025 11:24:30 -0800 Subject: [PATCH 01/16] 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 551a176d72a8a4e5317f8dfb834d2d4ec8736ef5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 13 Dec 2025 11:37:52 -0800 Subject: [PATCH 02/16] 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 c01c04924beb6ac97e303d9bc7670100e0ff9572 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 14 Dec 2025 12:12:10 -0800 Subject: [PATCH 03/16] 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 eecb4b0e071766fb42f74e04b8b176d4f7d3696b Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 14 Dec 2025 12:47:11 -0800 Subject: [PATCH 04/16] 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 a4b4c97e166e4e7a49548c863176b3b798e00e00 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 14 Dec 2025 12:47:17 -0800 Subject: [PATCH 05/16] 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 f7640f11ed7ec0ea6689c270cf55dbe398cf9bf8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 12:13:10 -0800 Subject: [PATCH 06/16] 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 3ddd63d4080387a8bdc497b63becb2106375c348 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 12:29:30 -0800 Subject: [PATCH 07/16] 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 acf29066e85eb62361161aa6ee09bf39609890e7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 13:39:22 -0800 Subject: [PATCH 08/16] 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 599abe35775e624d7e58e71101682504f790deac Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 14:39:23 -0800 Subject: [PATCH 09/16] 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 cc7704be84448ab3c87d36b4f1c483a9b2d11caf Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 14:45:00 -0800 Subject: [PATCH 10/16] 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 1e45e58ab48f88b36f0f9d262941f28bb31bc82a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 14:47:41 -0800 Subject: [PATCH 11/16] 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 90877658220854f443530c3e4b0f55db13580067 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 15:32:18 -0800 Subject: [PATCH 12/16] 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 160675365a6a79435b69ee0aabd0305d2ec8f508 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 15:58:43 -0800 Subject: [PATCH 13/16] Fix typos Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Extensions/TypeSignatureExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs b/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs index cc0a7fbcc..ca7b32cd0 100644 --- a/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs +++ b/src/WinRT.Interop.Generator/Extensions/TypeSignatureExtensions.cs @@ -47,7 +47,7 @@ public bool IsFullyResolvable([NotNullWhen(true)] out TypeDefinition? definition } /// - /// Enumerates all interface types implementation by the specified type, including those implemented by base types. + /// Enumerates all interface types implemented by the specified type, including those implemented by base types. /// /// The sequence of interface types implemented by the input type. /// @@ -84,7 +84,7 @@ public IEnumerable EnumerateAllInterfaces() yield return interfaceSignature.InstantiateGenericTypes(context); // 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). + // signatures for base interfaces here: they will be already instantiated when returned). foreach (TypeSignature baseInterface in interfaceSignature.EnumerateAllInterfaces()) { yield return baseInterface; From 1e083fbd635cb86f5b0c3249986c0fcbffc0cd26 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 20:21:05 -0800 Subject: [PATCH 14/16] Add tests for generic type activation and interface QI Introduced new test cases to validate activation and interface QueryInterface (QI) for constructed generic types and their derived types. Refactored QI logic into a reusable ComHelpers.EnsureQueryInterface method. Added several generic and derived classes to exercise interface exposure and activation scenarios. --- .../ClassActivation/Program.cs | 173 ++++++++++++++++-- 1 file changed, 161 insertions(+), 12 deletions(-) diff --git a/src/Tests/FunctionalTests/ClassActivation/Program.cs b/src/Tests/FunctionalTests/ClassActivation/Program.cs index 69b6db59a..95ff89205 100644 --- a/src/Tests/FunctionalTests/ClassActivation/Program.cs +++ b/src/Tests/FunctionalTests/ClassActivation/Program.cs @@ -1,8 +1,14 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; +using System.Runtime.Versioning; +using System.Windows.Input; using TestComponent; using TestComponentCSharp; +using Windows.Foundation.Collections; using WindowsRuntime.InteropServices; CustomDisposableTest customDisposableTest = new(); @@ -38,8 +44,6 @@ { void* testMixedComClassUnknownPtr = WindowsRuntimeMarshal.ConvertToUnmanaged(testMixedComClass); void* classicComActionPtr = null; - void* closablePtr = null; - void* inspectablePtr = null; try { @@ -53,21 +57,61 @@ Marshal.ThrowExceptionForHR(((delegate* unmanaged[MemberFunction])(*(void***)classicComActionPtr)[3])(classicComActionPtr)); // Sanity check: we should still also be able to 'QueryInterface' for other interfaces - Marshal.ThrowExceptionForHR(Marshal.QueryInterface( - pUnk: (nint)testMixedComClassUnknownPtr, - iid: new Guid("30D5A829-7FA4-4026-83BB-D75BAE4EA99E"), - ppv: out *(nint*)&closablePtr)); - Marshal.ThrowExceptionForHR(Marshal.QueryInterface( - pUnk: (nint)testMixedComClassUnknownPtr, - iid: new Guid("AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90"), - ppv: out *(nint*)&inspectablePtr)); + ComHelpers.EnsureQueryInterface( + unknownPtr: testMixedComClassUnknownPtr, + iids: [ + new Guid("30D5A829-7FA4-4026-83BB-D75BAE4EA99E"), + new Guid("AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90")]); } finally { WindowsRuntimeMarshal.Free(testMixedComClassUnknownPtr); WindowsRuntimeMarshal.Free(classicComActionPtr); - WindowsRuntimeMarshal.Free(closablePtr); - WindowsRuntimeMarshal.Free(inspectablePtr); + } +} + +ConstructedDerivedType constructedDerivedType = new(); + +unsafe +{ + void* constructedDerivedTypePtr = WindowsRuntimeMarshal.ConvertToUnmanaged(constructedDerivedType); + + try + { + ComHelpers.EnsureQueryInterface( + unknownPtr: constructedDerivedTypePtr, + iids: [ + new Guid("E2FCC7C1-3BFC-5A0B-B2B0-72E769D1CB7E"), // 'IEnumerable' + new Guid("07F69483-8097-5F92-BB53-068DC81F281A"), // 'IEnumerable' + new Guid("E8A8353A-767E-5C47-97CF-E0336C354311"), // 'IClosable' + new Guid("8311ED02-4F46-5CAF-BF01-2AE354C04BF5")]); // 'IMapChangedEventArgs' + } + finally + { + WindowsRuntimeMarshal.Free(constructedDerivedTypePtr); + } +} + +object genericType = GenericFactory.Make(); + +unsafe +{ + void* genericTypePtr = WindowsRuntimeMarshal.ConvertToUnmanaged(genericType); + + try + { + ComHelpers.EnsureQueryInterface( + unknownPtr: genericTypePtr, + iids: [ + new Guid("30160817-1D7D-54E9-99DB-D7636266A476"), // 'IEnumerable' + new Guid("07F69483-8097-5F92-BB53-068DC81F281A"), // 'IEnumerable' + new Guid("907661AB-C065-5A14-9AC3-2FEB0D164DDA"), // 'IReadOnlyDictionary' + new Guid("3631E370-2F65-5F4A-8364-1619C536DB12"), // 'IEnumerable>' + new Guid("F61E8483-D7A0-5840-9DCF-40423CCC97D0")]); // 'IMapChangedEventArgs' + } + finally + { + WindowsRuntimeMarshal.Free(genericTypePtr); } } @@ -86,6 +130,87 @@ public void Dispose() } } +class GenericBaseType : IEnumerable, IDisposable +{ + public void Dispose() + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} + +// This type is extending a generic type. The base type is a 'TypeSpec' with a constructed +// generic type. We use this to validate that the base constructed interfaces are seen. +class ConstructedDerivedType : GenericBaseType, IMapChangedEventArgs +{ + public CollectionChange CollectionChange => throw new NotImplementedException(); + + public IEnumerable Key => throw new NotImplementedException(); +} + +class GenericType : IEnumerable, IReadOnlyDictionary, IMapChangedEventArgs +{ + public T2 this[T1 key] => throw new NotImplementedException(); + + public IEnumerable Keys => throw new NotImplementedException(); + + public IEnumerable Values => throw new NotImplementedException(); + + public int Count => throw new NotImplementedException(); + + public CollectionChange CollectionChange => throw new NotImplementedException(); + + public T2 Key => throw new NotImplementedException(); + + public bool ContainsKey(T1 key) + { + throw new NotImplementedException(); + } + + public IEnumerator GetEnumerator() + { + throw new NotImplementedException(); + } + + public bool TryGetValue(T1 key, [MaybeNullWhen(false)] out T2 value) + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + throw new NotImplementedException(); + } +} + +class GenericFactory +{ + // This method is caling a generic one, which then constructs a generic type. + // The 'GenericType' instantiation doesn't appear anywhere in the .dll, + // but we should be able to still find it, because: + // - We can see 'Make' in the 'MethodSpec' table. + // - We can inspect all instructions in that constructed method + // - We can see a 'newobj' instruction with a 'MemberRef' to 'GenericType.ctor' + // From there, we should be able to gather info on that constructed generic type. + public static object Make() => Make(); + + private static object Make() => new GenericType(); +} + [Guid("3C832AA5-5F7E-46EE-B1BF-7FE03AE866AF")] [GeneratedComInterface] partial interface IClassicComAction @@ -93,6 +218,30 @@ partial interface IClassicComAction void Invoke(); } +file static class ComHelpers +{ + [SupportedOSPlatform("windows6.3")] + public static unsafe void EnsureQueryInterface(void* unknownPtr, params ReadOnlySpan iids) + { + foreach (Guid iid in iids) + { + void* interfacePtr = null; + + try + { + Marshal.ThrowExceptionForHR(Marshal.QueryInterface( + pUnk: (nint)unknownPtr, + iid: iid, + ppv: out *(nint*)&interfacePtr)); + } + finally + { + WindowsRuntimeMarshal.Free(interfacePtr); + } + } + } +} + /* // new RCW / Factory activation var instance = new Class(); From 54cf7306f6a210e0598fd1b3938e29428dadffc9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 15 Dec 2025 20:47:59 -0800 Subject: [PATCH 15/16] Add tests for AsyncInfo.Run with generic async operations Introduces tests for IAsyncActionWithProgress and IAsyncOperation using AsyncInfo.Run with explicit and transitive type arguments. Ensures correct COM interface querying and memory management for these async operations. --- .../ClassActivation/Program.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/Tests/FunctionalTests/ClassActivation/Program.cs b/src/Tests/FunctionalTests/ClassActivation/Program.cs index 95ff89205..62c7b5ccf 100644 --- a/src/Tests/FunctionalTests/ClassActivation/Program.cs +++ b/src/Tests/FunctionalTests/ClassActivation/Program.cs @@ -5,10 +5,13 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using System.Runtime.Versioning; +using System.Threading.Tasks; using System.Windows.Input; using TestComponent; using TestComponentCSharp; +using Windows.Foundation; using Windows.Foundation.Collections; +using Windows.Foundation.Tasks; using WindowsRuntime.InteropServices; CustomDisposableTest customDisposableTest = new(); @@ -115,6 +118,46 @@ } } +IAsyncActionWithProgress asyncActionWithProgress = GenericFactory.MakeAsyncActionWithProgress(); + +unsafe +{ + void* asyncActionWithProgressPtr = WindowsRuntimeMarshal.ConvertToUnmanaged(asyncActionWithProgress); + + try + { + ComHelpers.EnsureQueryInterface( + unknownPtr: asyncActionWithProgressPtr, + iids: [ + new Guid("0EDE398F-0090-574E-AD30-E152B433BF6A"), // 'IAsyncActionWithProgress' + new Guid("0DB2462F-B6D6-5A6C-8834-B530BAAA45FD")]); // 'IAsyncInfo' + } + finally + { + WindowsRuntimeMarshal.Free(asyncActionWithProgressPtr); + } +} + +IAsyncOperation asyncOperation = GenericFactory.MakeAsyncOperation(); + +unsafe +{ + void* asyncOperationPtr = WindowsRuntimeMarshal.ConvertToUnmanaged(asyncOperation); + + try + { + ComHelpers.EnsureQueryInterface( + unknownPtr: asyncOperationPtr, + iids: [ + new Guid("1AE01209-1ACA-51D3-A080-8B1214E0A39E"), // 'IAsyncOperation' + new Guid("0DB2462F-B6D6-5A6C-8834-B530BAAA45FD")]); // 'IAsyncInfo' + } + finally + { + WindowsRuntimeMarshal.Free(asyncOperationPtr); + } +} + sealed class TestComposable : Composable { } @@ -209,6 +252,26 @@ class GenericFactory public static object Make() => Make(); private static object Make() => new GenericType(); + + // Specific test for 'AsyncInfo.Run' with explicit type arguments + [SupportedOSPlatform("windows10.0.10240.0")] + public static IAsyncActionWithProgress MakeAsyncActionWithProgress() + { + return AsyncInfo.Run((token, progress) => Task.CompletedTask); + } + + // Specific test for 'AsyncInfo.Run' with transitive type arguments + [SupportedOSPlatform("windows10.0.10240.0")] + public static IAsyncOperation MakeAsyncOperation() + { + return MakeAsyncActionOperation(); + } + + [SupportedOSPlatform("windows10.0.10240.0")] + private static IAsyncOperation MakeAsyncActionOperation() + { + return AsyncInfo.Run(token => Task.FromResult(default(T))); + } } [Guid("3C832AA5-5F7E-46EE-B1BF-7FE03AE866AF")] From 4147e26ef4257f9513bd3e18f7c175345b6ce627 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 17 Dec 2025 19:20:41 -0800 Subject: [PATCH 16/16] Remove constructed generic type checks in discovery Eliminated filtering of constructed generic type signatures using IsConstructedGenericTypeVisitor in the type and array discovery logic. This simplifies the code and ensures all type signatures are processed for marshalling code generation. --- .../Generation/InteropGenerator.Discover.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs index cd13d6761..688968d69 100644 --- a/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs +++ b/src/WinRT.Interop.Generator/Generation/InteropGenerator.Discover.cs @@ -14,7 +14,6 @@ using WindowsRuntime.InteropGenerator.Models; using WindowsRuntime.InteropGenerator.References; using WindowsRuntime.InteropGenerator.Resolvers; -using WindowsRuntime.InteropGenerator.Visitors; namespace WindowsRuntime.InteropGenerator.Generation; @@ -221,13 +220,6 @@ private static void DiscoverGenericTypeInstantiations( { args.Token.ThrowIfCancellationRequested(); - // 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)) - { - continue; - } - // Track the constructed generic type (if it's not applicable, it will be a no-op) InteropTypeDiscovery.TryTrackGenericTypeInstance( typeSignature: typeSignature, @@ -262,13 +254,6 @@ private static void DiscoverSzArrayTypes( { args.Token.ThrowIfCancellationRequested(); - // 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)) - { - continue; - } - // Track the SZ array type (if it's not applicable, it will be a no-op) InteropTypeDiscovery.TryTrackSzArrayType( typeSignature: typeSignature,