Skip to content
Open
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
49 changes: 41 additions & 8 deletions src/Authoring/WinRT.SourceGenerator/WinRTTypeWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1552,7 +1552,9 @@ private void AddOverloadAttribute(EntityHandle methodHandle, string methodName)

private void AddOverloadAttributeForInterfaceMethods(TypeDeclaration interfaceTypeDeclaration)
{
// Generate unique names for any overloaded methods
// Generate unique names for any overloaded methods.
// If the user has applied [OverloadAttribute("name")] on a method, honor that name
// instead of auto-generating a sequential suffix.
foreach (var methodName in interfaceTypeDeclaration.MethodsByName.Where(symbol => symbol.Value.Count > 1))
{
var methodSymbols = methodName.Value.Where(symbol => symbol is IMethodSymbol);
Expand All @@ -1569,10 +1571,34 @@ private void AddOverloadAttributeForInterfaceMethods(TypeDeclaration interfaceTy
continue;
}

string overloadedMethodName = methodName.Key + (++lastSuffix);
while (interfaceTypeDeclaration.MethodsByName.ContainsKey(overloadedMethodName))
// Check if the user explicitly applied [Windows.Foundation.Metadata.OverloadAttribute("...")]
// and honor the user-specified name instead of auto-generating one.
string overloadedMethodName = null;
if (method is IMethodSymbol methodSymbol)
{
var userOverloadAttr = methodSymbol.GetAttributes()
.FirstOrDefault(a =>
a.AttributeClass?.ToDisplayString() == "Windows.Foundation.Metadata.OverloadAttribute");
if (userOverloadAttr != null
&& userOverloadAttr.ConstructorArguments is { IsDefaultOrEmpty: false }
&& userOverloadAttr.ConstructorArguments[0].Value is string userSpecifiedName)
{
overloadedMethodName = userSpecifiedName;
Logger.Log("Using user-specified overload name for " + methodSymbol.Name + ": " + overloadedMethodName);
}
else if (userOverloadAttr != null)
{
Logger.Log("warning: [OverloadAttribute] found on " + methodSymbol.Name + " but could not extract name; auto-generating");
}
}

if (overloadedMethodName == null)
{
overloadedMethodName = methodName.Key + (++lastSuffix);
while (interfaceTypeDeclaration.MethodsByName.ContainsKey(overloadedMethodName))
{
overloadedMethodName = methodName.Key + (++lastSuffix);
}
}

Logger.Log("Overloading " + methodName.Key + " with " + overloadedMethodName);
Expand Down Expand Up @@ -1717,6 +1743,13 @@ private void AddCustomAttributes(IEnumerable<AttributeData> attributes, EntityHa
continue;
}

// OverloadAttribute is emitted by AddOverloadAttributeForInterfaceMethods;
// skip any user-applied instance to avoid duplicates in the winmd.
if (attributeType.ToString() == "Windows.Foundation.Metadata.OverloadAttribute")
{
continue;
}

// Skip the [GeneratedBindableCustomProperty] attribute. It is valid to add this on types in WinRT
// components (if they need to be exposed and implement ICustomPropertyProvider), but the attribute
// should never show up in the .winmd file (it would also cause build errors in the projections).
Expand Down Expand Up @@ -2529,11 +2562,11 @@ void AddType(INamedTypeSymbol type, bool treatAsProjectedType = false)
{
// Prioritize any mapped types before treating an attribute as a projected type.
AddProjectedType(type);
}
// Check if CsWinRT component projected type from another project.
else if (Model.Compilation.GetTypeByMetadataName(GeneratorHelper.GetAuthoringMetadataTypeName(qualifiedName)) != null)
{
AddProjectedType(type);
}
// Check if CsWinRT component projected type from another project.
else if (Model.Compilation.GetTypeByMetadataName(GeneratorHelper.GetAuthoringMetadataTypeName(qualifiedName)) != null)
{
AddProjectedType(type);
}
else
{
Expand Down
45 changes: 45 additions & 0 deletions src/Tests/AuthoringConsumptionTest/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,51 @@ TEST(AuthoringTest, CustomInterfaceGuid)
EXPECT_EQ(customInterface.HelloWorld(), L"Hello World!");
}

TEST(AuthoringTest, CustomOverloadNames)
{
// Test interface-level overloads with user-specified OverloadAttribute names.
CustomOverloadNamesClass obj;
EXPECT_EQ(obj.Lookup(hstring(L"test")), hstring(L"found:test"));
EXPECT_EQ(obj.Lookup(42), 420);

// Through the interface
ICustomOverloadNames iface = obj;
EXPECT_EQ(iface.Lookup(hstring(L"hello")), hstring(L"found:hello"));
EXPECT_EQ(iface.Lookup(7), 70);

// Test class-level overloads with mixed user-specified and auto-generated names.
EXPECT_EQ(obj.Transform(hstring(L"abc")), hstring(L"ABC"));
EXPECT_EQ(obj.Transform(5), 10);
EXPECT_EQ(obj.Transform(1.0), 1.5);

// Verify the user-specified ABI names actually appear in the generated projection.
// The abi<ICustomOverloadNames>::type vtable has virtual methods named exactly
// after the [Overload("...")] values. If the winmd had auto-generated names
// (e.g. "Lookup2") instead of "LookupByIndex", these calls would fail to compile.
using abi_type = winrt::impl::abi_t<ICustomOverloadNames>;
auto raw = static_cast<abi_type*>(winrt::get_abi(iface));
{
int32_t result = 0;
EXPECT_EQ(raw->LookupByIndex(7, &result), S_OK);
EXPECT_EQ(result, 70);
}
{
bool result = false;
EXPECT_EQ(raw->LookupByFlag(false, &result), S_OK);
EXPECT_TRUE(result);
}

// Same check for the class-level synthesized interface.
using class_abi_type = winrt::impl::abi_t<ICustomOverloadNamesClassClass>;
ICustomOverloadNamesClassClass classIface = obj.as<ICustomOverloadNamesClassClass>();
auto rawClass = static_cast<class_abi_type*>(winrt::get_abi(classIface));
{
int32_t result = 0;
EXPECT_EQ(rawClass->TransformNumber(5, &result), S_OK);
EXPECT_EQ(result, 10);
}
}

TEST(AuthoringTest, NonActivatableFactory)
{
EXPECT_EQ(NonActivatableFactory::Create().GetText(), L"Test123");
Expand Down
31 changes: 31 additions & 0 deletions src/Tests/AuthoringTest/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1969,6 +1969,37 @@ public sealed class CustomInterfaceGuidClass : ICustomInterfaceGuid
public string HelloWorld() => "Hello World!";
}

// Test that user-specified [OverloadAttribute] names are emitted in the winmd.
// The interface has 3 overloads of Lookup where the non-default ones carry custom ABI names.
public interface ICustomOverloadNames
{
[Windows.Foundation.Metadata.DefaultOverload()]
string Lookup(string key);

[Windows.Foundation.Metadata.Overload("LookupByIndex")]
int Lookup(int index);

[Windows.Foundation.Metadata.Overload("LookupByFlag")]
bool Lookup(bool flag);
}

// Test a mix of user-specified and auto-generated OverloadAttribute names.
public sealed class CustomOverloadNamesClass : ICustomOverloadNames
{
public string Lookup(string key) => "found:" + key;
public int Lookup(int index) => index * 10;
public bool Lookup(bool flag) => !flag;

// Class-level overloads: first gets custom name, second auto-generated.
[Windows.Foundation.Metadata.DefaultOverload()]
public string Transform(string input) => input.ToUpper();

[Windows.Foundation.Metadata.Overload("TransformNumber")]
public int Transform(int value) => value * 2;

public double Transform(double value) => value + 0.5;
}

public sealed class NonActivatableType
{
private readonly string _text;
Expand Down