Skip to content
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
16 changes: 14 additions & 2 deletions src/Tests/AuthoringConsumptionTest/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -977,10 +977,22 @@ TEST(AuthoringTest, AsyncMethodClass)
TEST(AuthoringTest, DeprecatedMembersClass)
{
DeprecatedMembersClass obj;

// Members marked [Deprecated(DeprecationType.Remove)] are omitted from the projection, so they are
// intentionally not referenced here. Their ABI vtable slot is still preserved (stubbed to E_NOTIMPL):
// the new members below sit after the removed slots in vtable order, so their correct dispatch confirms
// the removed slots remain in place and the layout did not shift. The deprecated members and the new
// members both remain projected and fully usable.
obj.OldMethod();
obj.NewMethod();
EXPECT_EQ(obj.OldProp(), L"");
EXPECT_EQ(obj.NewProp(), L"");
EXPECT_EQ(obj.OldProp(), L"OldProp");
EXPECT_EQ(obj.NewProp(), L"NewProp");

DeprecatedMembersClass::OldStatic();
DeprecatedMembersClass::NewStatic();

auto oldToken = obj.OldEvent(auto_revoke, [](IInspectable const&, int32_t const&) {});
auto newToken = obj.NewEvent(auto_revoke, [](IInspectable const&, int32_t const&) {});
}

TEST(AuthoringTest, FullFeaturedClass)
Expand Down
26 changes: 24 additions & 2 deletions src/Tests/AuthoringTest/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2271,12 +2271,34 @@ public sealed class DeprecatedMembersClass
[Windows.Foundation.Metadata.Deprecated("Use NewMethod instead", Windows.Foundation.Metadata.DeprecationType.Deprecate, 1u)]
public void OldMethod() { }

[Windows.Foundation.Metadata.Deprecated("RemovedMethod is gone", Windows.Foundation.Metadata.DeprecationType.Remove, 2u)]
public void RemovedMethod() { }

public void NewMethod() { }

[Windows.Foundation.Metadata.Deprecated("Use NewStatic instead", Windows.Foundation.Metadata.DeprecationType.Deprecate, 1u)]
public static void OldStatic() { }

[Windows.Foundation.Metadata.Deprecated("RemovedStatic is gone", Windows.Foundation.Metadata.DeprecationType.Remove, 2u)]
public static void RemovedStatic() { }

public static void NewStatic() { }

[Windows.Foundation.Metadata.Deprecated("Use NewProp instead", Windows.Foundation.Metadata.DeprecationType.Deprecate, 1u)]
public string OldProp => "";
public string OldProp => "OldProp";

[Windows.Foundation.Metadata.Deprecated("RemovedProp is gone", Windows.Foundation.Metadata.DeprecationType.Remove, 2u)]
public string RemovedProp => "RemovedProp";

public string NewProp => "NewProp";

[Windows.Foundation.Metadata.Deprecated("Use NewEvent instead", Windows.Foundation.Metadata.DeprecationType.Deprecate, 1u)]
public event System.EventHandler<int> OldEvent;

[Windows.Foundation.Metadata.Deprecated("RemovedEvent is gone", Windows.Foundation.Metadata.DeprecationType.Remove, 2u)]
public event System.EventHandler<int> RemovedEvent;

public string NewProp => "";
public event System.EventHandler<int> NewEvent;
}

// Class implementing INotifyPropertyChanged + custom interface
Expand Down
60 changes: 60 additions & 0 deletions src/Tests/TestComponentCSharp/DeprecatedClasses.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#include "pch.h"
#include "DeprecatedClasses.h"
#include "DeprecatedConstructorClass.g.cpp"
#include "RemovedActivationClass.g.cpp"
#include "RemovedComposableClass.g.cpp"

namespace winrt::TestComponentCSharp::implementation
{
// The value encodes which constructor ran, so a test can prove the surviving constructor still
// dispatches through its original factory slot even though the one before it was removed.
DeprecatedConstructorClass::DeprecatedConstructorClass(int32_t first)
{
m_value = first;
}

DeprecatedConstructorClass::DeprecatedConstructorClass(int32_t first, int32_t second)
{
m_value = first + second;
}

DeprecatedConstructorClass::DeprecatedConstructorClass(int32_t first, int32_t second, int32_t third)
{
m_value = first + second + third;
}

int32_t DeprecatedConstructorClass::Value()
{
return m_value;
}

RemovedActivationClass::RemovedActivationClass(int32_t initialValue)
{
m_value = initialValue;
}

TestComponentCSharp::RemovedActivationClass RemovedActivationClass::Create(int32_t initialValue)
{
return winrt::make<RemovedActivationClass>(initialValue);
}

int32_t RemovedActivationClass::Value()
{
return m_value;
}

TestComponentCSharp::RemovedComposableClass RemovedComposableClass::Create(int32_t initialValue)
{
return winrt::make<RemovedComposableClass>(initialValue);
}

RemovedComposableClass::RemovedComposableClass(int32_t initialValue)
{
m_value = initialValue;
}

int32_t RemovedComposableClass::Value()
{
return m_value;
}
}
62 changes: 62 additions & 0 deletions src/Tests/TestComponentCSharp/DeprecatedClasses.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once
#include "DeprecatedConstructorClass.g.h"
#include "RemovedActivationClass.g.h"
#include "RemovedComposableClass.g.h"

namespace winrt::TestComponentCSharp::implementation
{
// Each constructor stores a distinct value so a test can tell which factory slot was
// actually dispatched to (the removed overloads keep their slot, but are not projected).
struct DeprecatedConstructorClass : DeprecatedConstructorClassT<DeprecatedConstructorClass>
{
DeprecatedConstructorClass(int32_t first);
DeprecatedConstructorClass(int32_t first, int32_t second);
DeprecatedConstructorClass(int32_t first, int32_t second, int32_t third);

int32_t Value();

private:
int32_t m_value{ 0 };
};

struct RemovedActivationClass : RemovedActivationClassT<RemovedActivationClass>
{
RemovedActivationClass(int32_t initialValue);

static TestComponentCSharp::RemovedActivationClass Create(int32_t initialValue);

int32_t Value();

private:
int32_t m_value{ 0 };
};

struct RemovedComposableClass : RemovedComposableClassT<RemovedComposableClass>
{
// The parameterless constructor is the one the (removed) composable factory method uses;
// the other is implementation-only, for the static factory method below
RemovedComposableClass() = default;
explicit RemovedComposableClass(int32_t initialValue);

static TestComponentCSharp::RemovedComposableClass Create(int32_t initialValue);

int32_t Value();

private:
int32_t m_value{ 0 };
};
}
namespace winrt::TestComponentCSharp::factory_implementation
{
struct DeprecatedConstructorClass : DeprecatedConstructorClassT<DeprecatedConstructorClass, implementation::DeprecatedConstructorClass>
{
};

struct RemovedActivationClass : RemovedActivationClassT<RemovedActivationClass, implementation::RemovedActivationClass>
{
};

struct RemovedComposableClass : RemovedComposableClassT<RemovedComposableClass, implementation::RemovedComposableClass>
{
};
}
50 changes: 50 additions & 0 deletions src/Tests/TestComponentCSharp/TestComponentCSharp.idl
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,56 @@ And this is another one"
Int32 Value{ get; set; };
}

// A constructor has no metadata row of its own: it is projected as a method on the activation
// (or composable) factory interface, and that method is where '[deprecated]' lives. These three
// classes cover the constructor cases the projection has to handle.

// A deprecated constructor is projected with '[Obsolete]'; a removed one is dropped from the
// projection while its factory vtable slot is preserved, so the constructor declared after it
// must still dispatch through the correct slot.
[default_interface]
runtimeclass DeprecatedConstructorClass
{
[deprecated("Use the three-argument constructor instead", deprecate, 1)]
DeprecatedConstructorClass(Int32 first);

[deprecated("The two-argument constructor is gone", remove, 1)]
DeprecatedConstructorClass(Int32 first, Int32 second);

DeprecatedConstructorClass(Int32 first, Int32 second, Int32 third);

Int32 Value{ get; };
}

// Every constructor is removed, so the (sealed, activatable) class cannot be constructed from the
// projection at all and instances come from the static factory method instead.
//
// Note: the constructor parameter cannot be named 'value'. MIDL projects a constructor as
// 'CreateInstance' on the activation factory interface and names its '[out, retval]' parameter
// 'value', so a user parameter of that name collides with it (MIDL5161).
[default_interface]
runtimeclass RemovedActivationClass
{
[deprecated("Use RemovedActivationClass.Create instead", remove, 1)]
RemovedActivationClass(Int32 initialValue);

static RemovedActivationClass Create(Int32 initialValue);

Int32 Value{ get; };
}

// As above, but unsealed, so its factory is composable rather than activatable.
[default_interface]
unsealed runtimeclass RemovedComposableClass
{
[deprecated("Use RemovedComposableClass.Create instead", remove, 1)]
RemovedComposableClass();

static RemovedComposableClass Create(Int32 initialValue);

Int32 Value{ get; };
}

// Compile time test for sub windows namespace
namespace Windows
{
Expand Down
2 changes: 2 additions & 0 deletions src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
<ClInclude Include="ClassWithExplicitIUnknown.h" />
<ClInclude Include="CustomExperimentClass.h" />
<ClInclude Include="CustomEquals.h" />
<ClInclude Include="DeprecatedClasses.h" />
<ClInclude Include="CustomIterableTest.h" />
<ClInclude Include="CustomReadOnlyDictionaryTest.h" />
<ClInclude Include="ManualProjectionTestClasses.h" />
Expand All @@ -111,6 +112,7 @@
<ClCompile Include="ClassWithExplicitIUnknown.cpp" />
<ClCompile Include="CustomExperimentClass.cpp" />
<ClCompile Include="CustomEquals.cpp" />
<ClCompile Include="DeprecatedClasses.cpp" />
<ClCompile Include="CustomIterableTest.cpp" />
<ClCompile Include="CustomReadOnlyDictionaryTest.cpp" />
<ClCompile Include="pch.cpp">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<ClCompile Include="AnotherAssembly.SetPropertyClass.cpp" />
<ClCompile Include="CustomEquals.cpp" />
<ClCompile Include="CustomExperimentClass.cpp" />
<ClCompile Include="DeprecatedClasses.cpp" />
<ClCompile Include="WinRT.Class.cpp" />
<ClCompile Include="ClassWithExplicitIUnknown.cpp" />
<ClCompile Include="NonUniqueClass.cpp" />
Expand All @@ -40,6 +41,7 @@
<ClInclude Include="AnotherAssembly.SetPropertyClass.h" />
<ClInclude Include="CustomEquals.h" />
<ClInclude Include="CustomExperimentClass.h" />
<ClInclude Include="DeprecatedClasses.h" />
<ClInclude Include="WinRT.Class.h" />
<ClInclude Include="ClassWithExplicitIUnknown.h" />
<ClInclude Include="NonUniqueClass.h" />
Expand Down
5 changes: 5 additions & 0 deletions src/Tests/UnitTest/ComInteropTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,13 @@ public void TestInputPane()
[TestMethod]
public void TestPlayToManager()
{
// 'PlayToManager' is deprecated in the Windows SDK, so it is projected with '[Obsolete]'. This
// test covers its interop extensions regardless, so suppress 'CS0618' to keep the intentional
// usage from breaking the build (warnings are treated as errors).
#pragma warning disable CS0618
Assert.ThrowsExactly<COMException>(() => PlayToManager.GetForWindow(new IntPtr(0)));
PlayToManager.ShowPlayToUIForWindow(new IntPtr(0));
#pragma warning restore CS0618
}

[TestMethod]
Expand Down
68 changes: 68 additions & 0 deletions src/Tests/UnitTest/TestComponentCSharp_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2000,6 +2000,74 @@ public void TestFactoriesWithExplicitlyImplementedIUnknown()
Assert.AreEqual(22, cls2.Value);
}

[TestMethod]
public void TestDeprecatedConstructors()
{
// A '[deprecated]' constructor is still projected, just with '[Obsolete]' on it
#pragma warning disable CS0618
var deprecated = new DeprecatedConstructorClass(1);
#pragma warning restore CS0618

Assert.AreEqual(1, deprecated.Value);

// The two-argument constructor is '[deprecated(remove)]', so it is not projected at all.
// Its factory vtable slot is preserved though, which is exactly what lets the three-argument
// constructor declared after it still dispatch through the right slot (it sums its arguments,
// so a wrong slot would either fail or produce a different value).
Assert.IsNull(typeof(DeprecatedConstructorClass).GetConstructor([typeof(int), typeof(int)]));

var live = new DeprecatedConstructorClass(1, 2, 3);

Assert.AreEqual(6, live.Value);

static bool IsObsolete(params Type[] parameterTypes)
{
ConstructorInfo constructor = typeof(DeprecatedConstructorClass).GetConstructor(parameterTypes);

Assert.IsNotNull(constructor);

return constructor.GetCustomAttribute<ObsoleteAttribute>() is not null;
}

Assert.IsTrue(IsObsolete(typeof(int)));
Assert.IsFalse(IsObsolete(typeof(int), typeof(int), typeof(int)));
}

[TestMethod]
public void TestRemovedConstructors()
{
// Every constructor of these two classes is '[deprecated(remove)]', so neither is
// constructible from the projection: the activatable (sealed) one and the composable
// (unsealed) one both have to drop their only constructor.
//
// 'HasPublicParameterlessConstructor' resolves the 'new()' constrained overload only when the
// type *as compiled against* really exposes a public parameterless constructor, so it asserts
// the reference projection's surface. That matters because dropping every constructor without
// emitting a non-public one in its place would let the C# compiler synthesize an implicit
// public parameterless constructor, which the implementation projection does not have.
Assert.IsTrue(HasPublicParameterlessConstructor<Class>());
Assert.IsFalse(HasPublicParameterlessConstructor<RemovedActivationClass>());
Assert.IsFalse(HasPublicParameterlessConstructor<RemovedComposableClass>());

// The implementation projection agrees with the reference projection above
Assert.AreEqual(0, typeof(RemovedActivationClass).GetConstructors().Length);
Assert.AreEqual(0, typeof(RemovedComposableClass).GetConstructors().Length);

// Both types are still fully usable through their static factory methods
Assert.AreEqual(42, RemovedActivationClass.Create(42).Value);
Assert.AreEqual(42, RemovedComposableClass.Create(42).Value);
}

/// <summary>
/// Compile-time probe for a public parameterless constructor: the <c>new()</c> constrained
/// overload is only a candidate when <typeparamref name="T"/> has one, so a call resolves to
/// the fallback overload otherwise.
/// </summary>
private static bool HasPublicParameterlessConstructor<T>() where T : new() => true;

/// <inheritdoc cref="HasPublicParameterlessConstructor{T}()"/>
private static bool HasPublicParameterlessConstructor<T>(int _ = 0) => false;

[TestMethod]
public void TestStaticMembers()
{
Expand Down
7 changes: 7 additions & 0 deletions src/WinRT.Projection.Writer/Builders/ProjectionFileBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,18 @@ public enum {{typeName}} : {{enumUnderlyingType}}
continue;
}

// Skip enum fields removed via '[Deprecated(..., DeprecationType.Remove, ...)]'
if (field.IsRemoved)
{
continue;
}

string fieldName = field.GetRawName();
string constantValue = field.Constant.FormatLiteral();

// Emits per-enum-field '[SupportedOSPlatform]' when the field has a '[ContractVersion]'
CustomAttributeFactory.WritePlatformAttribute(writer, context, field);
CustomAttributeFactory.WriteObsoleteAttribute(writer, field);

writer.WriteLine($"{fieldName} = unchecked(({enumUnderlyingType}){constantValue}),");
}
Expand Down
Loading