Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
964e711
Refactor IReadOnlyList method definitions to accept MemberReference
Sergio0694 Dec 28, 2025
0d3f282
Add IListAdapter<T> and IListAdapterExtensions
Sergio0694 Dec 28, 2025
7d45623
Add IListAdapter<T> support to IList1 implementation
Sergio0694 Dec 28, 2025
9a9ced3
Add GetView method to IListAdapter<T>
Sergio0694 Dec 28, 2025
f266a1f
Add GetView support for IList<T> interop generation
Sergio0694 Dec 28, 2025
41ac7f1
Prevent infinite recursion in user-defined type discovery
Sergio0694 Dec 28, 2025
2b92404
Add IListAdapter1 IndexOf support to interop generator
Sergio0694 Dec 29, 2025
57a1ec9
Add SetAt support to IList interop generation
Sergio0694 Dec 30, 2025
7540acf
Remove unused variable and reorder IListAdapter1SetAt method
Sergio0694 Dec 30, 2025
1747a86
Add InsertAt support for IList<T> interop
Sergio0694 Dec 30, 2025
4e3227c
Move IndexOf method in IListAdapter<T>
Sergio0694 Dec 30, 2025
47033d6
Add RemoveAt support to IList<T> interop
Sergio0694 Dec 30, 2025
78ae3a1
Add support for IList<T>.Append interop method
Sergio0694 Dec 30, 2025
8bc00fe
Add RemoveAtEnd support for IList<T> interop
Sergio0694 Dec 30, 2025
4e0cc22
Add support for IList<T>.Clear interop method
Sergio0694 Dec 30, 2025
6724b05
Add GetMany method support for IList<T> interop
Sergio0694 Dec 30, 2025
a34bb6a
Add support for managed value method rewrites
Sergio0694 Dec 30, 2025
260e669
Add CreateLdind method to CilInstructionExtensions
Sergio0694 Dec 30, 2025
f754540
Add ReplaceAll method to IList1 interop implementation
Sergio0694 Dec 30, 2025
3064471
Refactor generic parameter usage in method signatures
Sergio0694 Jan 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,92 @@ public static void ImplType(
ModuleDefinition module,
out TypeDefinition implType)
{
TypeSignature elementType = listType.TypeArguments[0];

// Define the 'GetAt' method
MethodDefinition getAtMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.GetAt(
readOnlyListType: listType,
getAtMethod: interopReferences.IListAdapter1GetAt(elementType),
interopReferences: interopReferences,
emitState: emitState,
module: module);

// Define the 'get_Size' method
MethodDefinition sizeMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.get_Size(
readOnlyListType: listType,
sizeMethod: interopReferences.IListAdapter1Size(elementType),
interopReferences: interopReferences,
module: module);

// Define the 'GetView' method
MethodDefinition getViewMethod = InteropMethodDefinitionFactory.IList1Impl.GetView(
listType: listType,
interopReferences: interopReferences,
emitState: emitState,
module: module);

// Define the 'IndexOf' method
MethodDefinition indexOfMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.IndexOf(
readOnlyListType: listType,
indexOfMethod: interopReferences.IListAdapter1IndexOf(elementType),
interopReferences: interopReferences,
emitState: emitState,
module: module);

// Define the 'SetAt' method
MethodDefinition setAtMethod = InteropMethodDefinitionFactory.IList1Impl.SetAt(
listType: listType,
interopReferences: interopReferences,
emitState: emitState,
module: module);

// Define the 'InsertAt' method
MethodDefinition insertAtMethod = InteropMethodDefinitionFactory.IList1Impl.InsertAt(
listType: listType,
interopReferences: interopReferences,
emitState: emitState,
module: module);

// Define the 'RemoveAt' method
MethodDefinition removeAtMethod = InteropMethodDefinitionFactory.IList1Impl.RemoveAt(
listType: listType,
interopReferences: interopReferences,
module: module);

// Define the 'Append' method
MethodDefinition appendMethod = InteropMethodDefinitionFactory.IList1Impl.Append(
listType: listType,
interopReferences: interopReferences,
emitState: emitState,
module: module);

// Define the 'RemoveAtEnd' method
MethodDefinition removeAtEndMethod = InteropMethodDefinitionFactory.IList1Impl.RemoveAtEnd(
listType: listType,
interopReferences: interopReferences,
module: module);

// Define the 'Clear' method
MethodDefinition clearMethod = InteropMethodDefinitionFactory.IList1Impl.Clear(
listType: listType,
interopReferences: interopReferences,
module: module);

// Define the 'GetMany' method
MethodDefinition getManyMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.GetMany(
readOnlyListType: listType,
getAtMethod: interopReferences.IListAdapter1GetAt(elementType),
interopReferences: interopReferences,
emitState: emitState,
module: module);

// Define the 'ReplaceAll' method
MethodDefinition replaceAllMethod = InteropMethodDefinitionFactory.IList1Impl.ReplaceAll(
listType: listType,
interopReferences: interopReferences,
emitState: emitState,
module: module);

Impl(
interfaceType: ComInterfaceType.InterfaceIsIInspectable,
ns: InteropUtf8NameFactory.TypeNamespace(listType),
Expand All @@ -991,7 +1077,19 @@ public static void ImplType(
interopReferences: interopReferences,
module: module,
implType: out implType,
vtableMethods: []);
vtableMethods: [
getAtMethod,
sizeMethod,
getViewMethod,
indexOfMethod,
setAtMethod,
insertAtMethod,
removeAtMethod,
appendMethod,
removeAtEndMethod,
clearMethod,
getManyMethod,
replaceAllMethod]);

// Track the type (it may be needed by COM interface entries for user-defined types)
emitState.TrackTypeDefinition(implType, listType, "Impl");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,29 +426,35 @@ public static void ImplType(
ModuleDefinition module,
out TypeDefinition implType)
{
TypeSignature elementType = readOnlyListType.TypeArguments[0];

// Define the 'GetAt' method
MethodDefinition getAtMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.GetAt(
readOnlyListType: readOnlyListType,
getAtMethod: interopReferences.IReadOnlyListAdapter1GetAt(elementType),
interopReferences: interopReferences,
emitState: emitState,
module: module);

// Define the 'get_Size' method
MethodDefinition sizeMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.get_Size(
readOnlyListType: readOnlyListType,
sizeMethod: interopReferences.IReadOnlyListAdapter1Size(elementType),
interopReferences: interopReferences,
module: module);

// Define the 'IndexOf' method
MethodDefinition indexOfMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.IndexOf(
readOnlyListType: readOnlyListType,
indexOfMethod: interopReferences.IReadOnlyListAdapter1IndexOf(elementType),
interopReferences: interopReferences,
emitState: emitState,
module: module);

// Define the 'GetMany' method
MethodDefinition getManyMethod = InteropMethodDefinitionFactory.IReadOnlyList1Impl.GetMany(
readOnlyListType: readOnlyListType,
getAtMethod: interopReferences.IReadOnlyListAdapter1GetAt(elementType),
interopReferences: interopReferences,
emitState: emitState,
module: module);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,20 @@ private static void TryTrackWindowsRuntimeGenericInterfaceTypeInstance(
else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IList1))
{
discoveryState.TrackIList1Type(typeSignature);

// Whenever we find an 'IList<T>' instantiation, we also need to track the corresponding 'IReadOnlyList<T>' instantiation.
// This is because that interface is needed to marshal the return value of the 'IVector<T>.GetView' method ('IVectorView<T>').
discoveryState.TrackIReadOnlyList1Type(interopReferences.IReadOnlyList1.MakeGenericReferenceType([.. typeSignature.TypeArguments]));

// We also need to track the constructed 'ReadOnlyCollection<T>' type, as that is used by 'IListAdapter<T>.GetView' in case the
// input 'IList<T>' instance doesn't implement 'IReadOnlyList<T>' directly. In that case, we return a 'ReadOnlyCollection<T>'
// object instead. This needs special handling because we won't analyze indirect (generated) calls into that adapter type.
TryTrackGenericTypeInstance(
typeSignature: interopReferences.ReadOnlyCollection1.MakeGenericReferenceType([.. typeSignature.TypeArguments]),
args: args,
discoveryState: discoveryState,
interopReferences: interopReferences,
module: module);
}
else if (SignatureComparer.IgnoreVersion.Equals(typeSignature.GenericType, interopReferences.IReadOnlyList1))
{
Expand Down
18 changes: 18 additions & 0 deletions src/WinRT.Interop.Generator/Discovery/InteropTypeDiscovery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,24 @@ public static void TryTrackExposedUserDefinedType(
return;
}

// Check if this is the first time that this user-defined type has been seen, and stop immediately if not.
// If the type has been seen before, it means that it either has already been fully processed, or that it
// is currently being processed (possibly by another thread, if multi-threading discovery is enabled). The
// reason for this check is not so much to improve performance (although it does avoid some repeated work),
// but most importantly to avoid stack overlows due to infinite recursion in cases where user-defined types
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'overlows' to 'overflows'.

Suggested change
// but most importantly to avoid stack overlows due to infinite recursion in cases where user-defined types
// but most importantly to avoid stack overflows due to infinite recursion in cases where user-defined types

Copilot uses AI. Check for mistakes.
// implement interfaces that then transitively required the same user-defined type to be tracked.
//
// For instance, consider a scenario where 'List<int>' is being discovered. While processing the implemented
// interfaces, 'IList<int>' will also be discovered. This will then require 'ReadOnlyCollection<int>' to be
// tracked, because it is used by the fallback code for the CCW implementation method of 'IVector<T>.GetView'.
// However, 'ReadOnlyCollection<int>' itself will also implement 'IList<int>', which would then require tracking
// 'ReadOnlyCollection<int>' itself again too, etc. That would just recurse forever without this check, because
// all those interfaces would keep being discovered before the initial processing of the type has finished.
if (!discoveryState.TryMarkUserDefinedType(typeSignature))
{
return;
}

// Reuse the thread-local builder to track all implemented interfaces for the current type
TypeSignatureEquatableSet.Builder interfaces = TypeSignatures ??= new TypeSignatureEquatableSet.Builder();

Expand Down
30 changes: 30 additions & 0 deletions src/WinRT.Interop.Generator/Extensions/CilInstructionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,38 @@ public static CilInstruction CreateStind(TypeSignature type, ModuleDefinition mo
ElementType.R4 => new CilInstruction(Stind_R4),
ElementType.R8 => new CilInstruction(Stind_R8),
ElementType.ValueType when type.Resolve() is { IsClass: true, IsEnum: true } => new CilInstruction(Stind_I4),
ElementType.I => new CilInstruction(Stind_I),
_ => new CilInstruction(Stobj, type.Import(module).ToTypeDefOrRef()),
};
}

/// <summary>
/// Create a new instruction loading a value indirectly from a target location.
/// </summary>
/// <param name="type">The type of value to load.</param>
/// <param name="module">The <see cref="ModuleDefinition"/> in use.</param>
/// <returns>The instruction.</returns>
[SuppressMessage("Style", "IDE0072", Justification = "We use 'ldobj' for all other possible types.")]
public static CilInstruction CreateLdind(TypeSignature type, ModuleDefinition module)
{
return type.ElementType switch
{
ElementType.Boolean => new CilInstruction(Ldind_I1),
ElementType.Char => new CilInstruction(Ldind_I2),
ElementType.I1 => new CilInstruction(Ldind_I1),
ElementType.U1 => new CilInstruction(Ldind_I1),
ElementType.I2 => new CilInstruction(Ldind_I2),
ElementType.U2 => new CilInstruction(Ldind_I2),
ElementType.I4 => new CilInstruction(Ldind_I4),
ElementType.U4 => new CilInstruction(Ldind_I4),
ElementType.I8 => new CilInstruction(Ldind_I8),
ElementType.U8 => new CilInstruction(Ldind_I8),
ElementType.R4 => new CilInstruction(Ldind_R4),
ElementType.R8 => new CilInstruction(Ldind_R8),
ElementType.ValueType when type.Resolve() is { IsClass: true, IsEnum: true } => new CilInstruction(Ldind_I4),
ElementType.I => new CilInstruction(Ldind_I),
_ => new CilInstruction(Ldobj, type.Import(module).ToTypeDefOrRef()),
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ public static MethodDefinition GetMany(
}

/// <summary>
/// Creates a <see cref="MethodDefinition"/> for the <c>get_Current</c> export method.
/// Creates a <see cref="MethodDefinition"/> for the <c>get_Current</c> or <c>MoveNext</c> export method.
/// </summary>
/// <param name="methodName">The name of the method to generate.</param>
/// <param name="adapterMethod">The adapter method to forward the call to.</param>
Expand Down Expand Up @@ -307,7 +307,7 @@ private static MethodDefinition HasCurrentOrMoveNext(
CilInstruction ldloc_0_returnHResult = new(Ldloc_0);
CilInstruction call_catchStartMarshalException = new(Call, interopReferences.RestrictedErrorInfoExceptionMarshallerConvertToUnmanaged.Import(module));

// Create a method body for the 'get_HasCurrent' method
// Create a method body for the method
boolMethod.CilMethodBody = new CilMethodBody()
{
// Declare 1 variable:
Expand Down
Loading
Loading