Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support nested types in AOT generator #1479

Merged
merged 3 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
92 changes: 86 additions & 6 deletions src/Authoring/WinRT.SourceGenerator/AotOptimizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private static string ToVtableLookupString(ISymbol symbol)

internal static VtableAttribute GetVtableAttributeToAdd(ITypeSymbol symbol, Func<ISymbol, bool> isWinRTType, IAssemblySymbol assemblySymbol, bool isAuthoring, string authoringDefaultInterface = "")
{
if (GeneratorHelper.HasNonInstantiatedWinRTGeneric(symbol) || GeneratorHelper.HasPrivateclass(symbol))
if (GeneratorHelper.HasNonInstantiatedWinRTGeneric(symbol))
{
return default;
}
Expand Down Expand Up @@ -161,10 +161,29 @@ internal static VtableAttribute GetVtableAttributeToAdd(ITypeSymbol symbol, Func
typeName = typeName[(@namespace.Length + 1)..];
}

EquatableArray<TypeInfo> classHierarchy = ImmutableArray<TypeInfo>.Empty;

// Gather the type hierarchy, only if the type is nested (as an optimization)
if (symbol.ContainingType is not null)
{
List<TypeInfo> hierarchyList = new();

for (ITypeSymbol parent = symbol; parent is not null; parent = parent.ContainingType)
{
hierarchyList.Add(new TypeInfo(
parent.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
parent.TypeKind,
parent.IsRecord));
}

classHierarchy = ImmutableArray.CreateRange(hierarchyList);
}

return new VtableAttribute(
isAuthoring ? "ABI.Impl." + @namespace : @namespace,
isGlobalNamespace,
typeName,
classHierarchy,
ToVtableLookupString(symbol),
interfacesToAddToVtable.ToImmutableArray(),
genericInterfacesToAddToVtable.ToImmutableArray(),
Expand Down Expand Up @@ -369,8 +388,6 @@ internal static void GenerateVtableAttributes(Action<string, string> addSource,
if (((isCsWinRTComponentFromAotOptimizer && !vtableAttribute.IsPublic) || !isCsWinRTComponentFromAotOptimizer) &&
vtableAttribute.Interfaces.Any())
{
var escapedClassName = GeneratorHelper.EscapeTypeNameForIdentifier(vtableAttribute.ClassName);

StringBuilder source = new();
source.AppendLine("using static WinRT.TypeExtensions;\n");
if (!vtableAttribute.IsGlobalNamespace)
Expand All @@ -381,12 +398,48 @@ namespace {{vtableAttribute.Namespace}}
""");
}

source.AppendLine($$"""
[global::WinRT.WinRTExposedType(typeof({{escapedClassName}}WinRTTypeDetails))]
partial class {{vtableAttribute.ClassName}}
var escapedClassName = GeneratorHelper.EscapeTypeNameForIdentifier(vtableAttribute.ClassName);

// Simple case when the type is not nested
if (vtableAttribute.ClassHierarchy.IsEmpty)
{
source.AppendLine($$"""
[global::WinRT.WinRTExposedType(typeof({{escapedClassName}}WinRTTypeDetails))]
partial class {{vtableAttribute.ClassName}}
{
}
""");
}
else
{
ReadOnlySpan<TypeInfo> classHierarchy = vtableAttribute.ClassHierarchy.AsSpan();

// If the type is nested, correctly nest the type definition
for (int i = classHierarchy.Length - 1; i > 0; i--)
{
source.AppendLine($$"""
partial {{classHierarchy[i].GetTypeKeyword()}} {{classHierarchy[i].QualifiedName}}
{
""");
}

// Define the inner-most item with the attribute
source.AppendLine($$"""
[global::WinRT.WinRTExposedType(typeof({{escapedClassName}}WinRTTypeDetails))]
partial {{classHierarchy[0].GetTypeKeyword()}} {{classHierarchy[0].QualifiedName}}
{
}
""");

// Close all brackets
for (int i = classHierarchy.Length - 1; i > 0; i--)
{
source.AppendLine("}");
}
}

source.AppendLine();
source.AppendLine($$"""
internal sealed class {{escapedClassName}}WinRTTypeDetails : global::WinRT.IWinRTExposedTypeDetails
{
public global::System.Runtime.InteropServices.ComWrappers.ComInterfaceEntry[] GetExposedInterfaces()
Expand Down Expand Up @@ -807,10 +860,37 @@ internal sealed record VtableAttribute(
string Namespace,
bool IsGlobalNamespace,
string ClassName,
EquatableArray<TypeInfo> ClassHierarchy,
string VtableLookupClassName,
EquatableArray<string> Interfaces,
EquatableArray<GenericInterface> GenericInterfaces,
bool IsArray,
bool IsDelegate,
bool IsPublic);

/// <summary>
/// A model describing a type info in a type hierarchy.
/// </summary>
/// <param name="QualifiedName">The qualified name for the type.</param>
/// <param name="Kind">The type of the type in the hierarchy.</param>
/// <param name="IsRecord">Whether the type is a record type.</param>
// Ported from https://github.com/Sergio0694/ComputeSharp
internal sealed record TypeInfo(string QualifiedName, TypeKind Kind, bool IsRecord)
{
/// <summary>
/// Gets the keyword for the current type kind.
/// </summary>
/// <returns>The keyword for the current type kind.</returns>
public string GetTypeKeyword()
{
return Kind switch
{
TypeKind.Struct when IsRecord => "record struct",
TypeKind.Struct => "struct",
TypeKind.Interface => "interface",
TypeKind.Class when IsRecord => "record",
_ => "class"
};
}
}
}
29 changes: 29 additions & 0 deletions src/Tests/AuthoringTest/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1575,6 +1575,35 @@ public partial struct PartialStruct
public double Z;
}

// Nested type to validate (https://github.com/microsoft/CsWinRT/issues/1477)
// Doesn't need to be consumed, we just want to verify the generator does work.
internal partial class Nested1
{
internal partial record struct Nested2
{
internal partial struct Nested3
{
internal partial interface INested4
{
internal partial record Nested5
{
internal partial class InnerMostType : IGraphicsEffectSource, IPublicInterface, IDisposable
{
public string HelloWorld()
{
return "Hello from mixed WinRT/COM";
}

public void Dispose()
{
}
}
}
}
}
}
}

public sealed class TestMixedWinRTCOMWrapper : IGraphicsEffectSource, IPublicInterface, IInternalInterface1, SomeInternalType.IInternalInterface2
{
public string HelloWorld()
Expand Down
52 changes: 51 additions & 1 deletion src/Tests/FunctionalTests/CCW/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,22 @@
return 115;
}

var nestedClass = TestClass2.GetInstance();
ccw = MarshalInspectable<object>.CreateMarshaler(nestedClass);
ccw.TryAs<IUnknownVftbl>(IID_IProperties2, out properties2CCW);
if (properties2CCW == null)
{
return 116;
}

var genericNestedClass = TestClass2.GetGenericInstance();
ccw = MarshalInspectable<object>.CreateMarshaler(genericNestedClass);
ccw.TryAs<IUnknownVftbl>(IID_IProperties2, out properties2CCW);
if (properties2CCW == null)
{
return 117;
}

var managedWarningClassList = new List<ManagedWarningClass>();
instance.BindableIterableProperty = managedWarningClassList;

Expand Down Expand Up @@ -343,4 +359,38 @@ sealed class NestedClass : IProperties2
private int _value;
public int ReadWriteProperty { get => _value; set => _value = value; }
}
}
}

partial class TestClass2
{
private partial class NestedTestClass : IProperties2
{
private int _value;
public int ReadWriteProperty { get => _value; set => _value = value; }
}

// Implements non WinRT generic interface to test WinRTExposedType attribute
// generated during these scenarios.
private partial class GenericNestedTestClass<T> : IProperties2, IComparer<T>
{
private int _value;
public int ReadWriteProperty { get => _value; set => _value = value; }

#nullable enable
public int Compare(T? x, T? y)
{
return 1;
}
#nullable restore
}

internal static IProperties2 GetInstance()
{
return new NestedTestClass();
}

internal static IProperties2 GetGenericInstance()
{
return new GenericNestedTestClass<int>();
}
}
Loading