diff --git a/src/Strings.props b/src/Strings.props
index 58b6aadc34..37828ead35 100644
--- a/src/Strings.props
+++ b/src/Strings.props
@@ -196,7 +196,7 @@
- %(AdditionalIncludeDirectories);$(IntDir)
+ %(ClCompile.AdditionalIncludeDirectories);$(IntDir)
diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h
index 9971ae62c8..77d28d07d5 100644
--- a/src/cswinrt/code_writers.h
+++ b/src/cswinrt/code_writers.h
@@ -14,6 +14,9 @@ namespace cswinrt
{
using namespace winmd::reader;
+ template
+ void write_obsolete_attribute(writer& w, T const& row);
+
static const struct
{
char const* csharp;
@@ -1206,6 +1209,11 @@ namespace cswinrt
return;
}
+ if (is_removed(method))
+ {
+ return;
+ }
+
auto access_spec = is_protected || is_overridable ? "protected " : "public ";
std::string method_spec = "";
@@ -1261,6 +1269,7 @@ namespace cswinrt
auto static_method_params = call_static_method.has_value() ? std::optional(std::pair(call_static_method.value(), method)) : std::nullopt;
if (!is_private)
{
+ write_obsolete_attribute(w, method);
write_method(w, signature, method.Name(), return_type,
static_method_params.has_value() ? w.write_temp("%", bind(static_method_params.value().first)) : interface_member,
access_spec, method_spec, platform_attribute, static_method_params);
@@ -1536,6 +1545,13 @@ remove => %;
void write_class_event(writer& w, Event const& event, TypeDef const& class_type, bool is_overridable, bool is_protected, std::string_view interface_member, std::string_view platform_attribute = ""sv, std::optional call_static_method = {})
{
+ auto [add, remove] = get_event_methods(event);
+ // MIDL places DeprecatedAttribute on the add method, not the Event row
+ if (is_removed(add))
+ {
+ return;
+ }
+
auto visibility = "public ";
if (is_protected)
@@ -1548,10 +1564,10 @@ remove => %;
visibility = "protected virtual ";
}
- auto [add, _] = get_event_methods(event);
bool is_private = is_implemented_as_private_method(w, class_type, add);
if (!is_private)
{
+ write_obsolete_attribute(w, add);
write_event(w, event.Name(), event,
call_static_method.has_value() ? w.write_temp("%", bind(call_static_method.value())) : interface_member,
visibility, ""sv, platform_attribute, call_static_method.has_value() ? std::optional(std::tuple(call_static_method.value(), event, false)) : std::nullopt);
@@ -1816,6 +1832,29 @@ remove => %;
return w.write_temp("%", bind(type.CustomAttribute()));
}
+ template
+ void write_obsolete_attribute(writer& w, T const& row)
+ {
+ if (is_deprecated_not_removed(row))
+ {
+ auto msg = get_deprecated_message(row);
+ if (!msg.empty())
+ {
+ w.write("[global::System.Obsolete(\"%\")]\n", msg);
+ }
+ else
+ {
+ w.write("[global::System.Obsolete]\n");
+ }
+ }
+ }
+
+ template
+ std::string write_obsolete_attribute_temp(writer& w, T const& row)
+ {
+ return w.write_temp("%", bind(row));
+ }
+
void write_custom_attributes(writer& w, std::pair const& custom_attributes, bool enable_platform_attrib)
{
std::map> attributes;
@@ -1876,6 +1915,7 @@ remove => %;
void write_type_custom_attributes(writer& w, TypeDef const& type, bool enable_platform_attrib)
{
write_custom_attributes(w, type.CustomAttribute(), enable_platform_attrib);
+ write_obsolete_attribute(w, type);
}
struct attributed_type
@@ -2104,6 +2144,10 @@ private static class _%
auto platform_attribute = write_platform_attribute_temp(w, factory_type);
for (auto&& method : factory_type.MethodList())
{
+ if (is_removed(method))
+ {
+ continue;
+ }
method_signature signature{ method };
if (settings.netstandard_compat)
{
@@ -2451,6 +2495,10 @@ Marshal.Release(inner);
{
return;
}
+ if (is_removed(method))
+ {
+ return;
+ }
method_signature signature{ method };
auto return_type = w.write_temp("%", [&](writer& w) {
write_projection_return_type(w, signature);
@@ -2498,6 +2546,11 @@ Marshal.Release(inner);
for (auto&& prop : factory.type.PropertyList())
{
auto [getter, setter] = get_property_methods(prop);
+ // MIDL places DeprecatedAttribute on getter method, not Property row
+ if (getter && is_removed(getter))
+ {
+ continue;
+ }
auto prop_type = write_prop_type(w, prop);
auto [prop_targets, inserted] = properties.try_emplace(std::string(prop.Name()),
@@ -3278,6 +3331,7 @@ visibility, self, objref_name);
{
std::set writtenInterfaces;
std::map>, std::optional>>> properties;
+ std::map property_deprecation;
auto fast_abi_class_val = get_fast_abi_class_for_class(type);
auto write_class_interface = [&](TypeDef const& interface_type, bool is_default_interface, bool is_overridable_interface, bool is_protected_interface, type_semantics semantics)
@@ -3334,6 +3388,11 @@ private % AsInternal(InterfaceTag<%> _) => % ?? Make_%();
{
MethodDef getter, setter;
std::tie(getter, setter) = get_property_methods(prop);
+ // MIDL places DeprecatedAttribute on the getter method, not the Property row
+ if (getter && is_removed(getter))
+ {
+ continue;
+ }
auto prop_type = write_prop_type(w, prop);
auto is_private = getter && is_implemented_as_private_method(w, type, getter); // for explicitly implemented interfaces, assume there is always a get.
auto property_name = is_private ? w.write_temp("%.%", interface_name, prop.Name()) : std::string(prop.Name());
@@ -3349,6 +3408,15 @@ private % AsInternal(InterfaceTag<%> _) => % ?? Make_%();
call_static_method && getter ? std::optional(std::pair(semantics_for_abi_call, prop)) : std::nullopt,
call_static_method && setter ? std::optional(std::pair(semantics_for_abi_call, prop)) : std::nullopt
);
+ if (inserted)
+ {
+ // Store the getter MethodDef for deprecation checking
+ // (MIDL places DeprecatedAttribute on getter, not Property row)
+ if (getter)
+ {
+ property_deprecation.try_emplace(property_name, getter);
+ }
+ }
if (!inserted)
{
auto& [property_type, getter_target, getter_platform, setter_target, setter_platform, is_overridable, is_public, _, getter_prop, setter_prop] = prop_targets->second;
@@ -3464,6 +3532,10 @@ private % AsInternal(InterfaceTag<%> _) => % ?? Make_%();
{
auto& [prop_type, getter_target, getter_platform, setter_target, setter_platform, is_overridable, is_public, is_private, getter_prop, setter_prop] = prop_data;
if (is_private) continue;
+ if (auto it = property_deprecation.find(prop_name); it != property_deprecation.end())
+ {
+ write_obsolete_attribute(w, it->second);
+ }
std::string_view access_spec = is_public ? "public "sv : "protected "sv;
std::string_view method_spec = is_overridable ? "virtual "sv : ""sv;
write_property(w, prop_name, prop_name, prop_type,
@@ -3648,6 +3720,11 @@ private % AsInternal(InterfaceTag<%> _) => % ?? Make_%();
void write_winrt_exposed_type_class(writer& w, TypeDef const& type, bool isFactory)
{
+ if (is_removed(type))
+ {
+ return;
+ }
+
if (should_write_winrt_exposed_type_attribute(type, isFactory))
{
if (get_category(type) == category::class_type && isFactory)
@@ -3909,7 +3986,16 @@ private static global::System.Runtime.CompilerServices.ConditionalWeakTable(get_type_semantics(evt.EventType()), typedef_name_type::Projected, false),
@@ -5022,6 +5129,11 @@ remove => %.Unsubscribe(value);
{
continue;
}
+ if (is_removed(method))
+ {
+ continue;
+ }
+ write_obsolete_attribute(w, method);
method_signature signature{ method };
auto [invoke_target, is_generic] = get_invoke_info(w, method);
w.write(R"(
@@ -5050,6 +5162,11 @@ remove => %.Unsubscribe(value);
for (auto&& prop : type.PropertyList())
{
+ if (is_removed(prop))
+ {
+ continue;
+ }
+ write_obsolete_attribute(w, prop);
auto [getter, setter] = get_property_methods(prop);
w.write(R"(
%unsafe % %%
@@ -5110,7 +5227,12 @@ return %;
int index = 0;
for (auto&& evt : type.EventList())
{
- auto semantics = get_type_semantics(evt.EventType());
+ if (is_removed(evt))
+ {
+ index++;
+ continue;
+ }
+ write_obsolete_attribute(w, evt);
auto event_source = w.write_temp(settings.netstandard_compat ? "_%" : "Get_%2()", evt.Name());
w.write(R"(
%event % %%
@@ -6540,6 +6662,23 @@ return 0;)",
bool have_generic_params = std::find_if(generic_abi_types.begin(), generic_abi_types.end(),
[](auto&& pair) { return !pair.second.empty(); }) != generic_abi_types.end();
+ // For removed methods, generate a stub that returns E_NOTIMPL.
+ // The vtable slot must exist for ABI compatibility, but the method
+ // is no longer on the projected interface so we can't call it.
+ if (is_removed(method))
+ {
+ w.write(R"(
+%
+private static unsafe int Do_Abi_%%
+{
+return unchecked((int)0x80004001); // E_NOTIMPL
+})",
+ !settings.netstandard_compat && !generic_type ? "[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]" : "",
+ vmethod_name,
+ bind(method));
+ return;
+ }
+
w.write(R"(
%
private static unsafe int Do_Abi_%%
@@ -6586,6 +6725,40 @@ bind_list(", ", signature.params()));
void write_property_abi_invoke(writer& w, Property const& prop)
{
auto [getter, setter] = get_property_methods(prop);
+ // MIDL places DeprecatedAttribute on getter, not Property row
+ if (getter && is_removed(getter))
+ {
+ // Property is removed from the projected interface, but the vtable slot
+ // must still exist for ABI compatibility. Generate E_NOTIMPL stubs.
+ auto generic_type = distance(prop.Parent().GenericParam()) > 0;
+ if (setter)
+ {
+ auto vmethod_name = get_vmethod_name(w, setter.Parent(), setter);
+ w.write(R"(
+%
+private static unsafe int Do_Abi_%%
+{
+return unchecked((int)0x80004001); // E_NOTIMPL
+})",
+ !settings.netstandard_compat && !generic_type ? "[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]" : "",
+ vmethod_name,
+ bind(setter));
+ }
+ if (getter)
+ {
+ auto vmethod_name = get_vmethod_name(w, getter.Parent(), getter);
+ w.write(R"(
+%
+private static unsafe int Do_Abi_%%
+{
+return unchecked((int)0x80004001); // E_NOTIMPL
+})",
+ !settings.netstandard_compat && !generic_type ? "[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]" : "",
+ vmethod_name,
+ bind(getter));
+ }
+ return;
+ }
auto type_name = write_type_name_temp(w, prop.Parent());
auto generic_type = distance(prop.Parent().GenericParam()) > 0;
if (setter)
@@ -6697,10 +6870,37 @@ prop.Name());
void write_event_abi_invoke(writer& w, Event const& evt)
{
+ auto [add_method, remove_method] = get_event_methods(evt);
+ // MIDL places DeprecatedAttribute on add method, not Event row
+ if (is_removed(add_method))
+ {
+ // Event is removed from the projected interface, but the vtable slots
+ // must still exist for ABI compatibility. Generate E_NOTIMPL stubs.
+ auto generic_type = distance(evt.Parent().GenericParam()) > 0;
+ auto add_vmethod_name = get_vmethod_name(w, add_method.Parent(), add_method);
+ auto remove_vmethod_name = get_vmethod_name(w, remove_method.Parent(), remove_method);
+ w.write(R"(
+%
+private static unsafe int Do_Abi_%%
+{
+return unchecked((int)0x80004001); // E_NOTIMPL
+}
+%
+private static unsafe int Do_Abi_%%
+{
+return unchecked((int)0x80004001); // E_NOTIMPL
+})",
+ !settings.netstandard_compat && !generic_type ? "[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]" : "",
+ add_vmethod_name,
+ bind(add_method),
+ !settings.netstandard_compat && !generic_type ? "[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]" : "",
+ remove_vmethod_name,
+ bind(remove_method));
+ return;
+ }
auto type_name = write_type_name_temp(w, evt.Parent());
auto generic_type = distance(evt.Parent().GenericParam()) > 0;
auto semantics = get_type_semantics(evt.EventType());
- auto [add_method, remove_method] = get_event_methods(evt);
auto add_signature = method_signature{ add_method };
auto handler_parameter_name = add_signature.params().back().first.Name();
@@ -7372,6 +7572,11 @@ IInspectableVftbl = global::WinRT.IInspectable.Vftbl.AbiToProjectionVftable,
void write_interface(writer& w, TypeDef const& type)
{
+ if (is_removed(type))
+ {
+ return;
+ }
+
XLANG_ASSERT(get_category(type) == category::interface_type);
auto type_name = write_type_name_temp(w, type, "%", typedef_name_type::CCW);
@@ -7394,6 +7599,11 @@ IInspectableVftbl = global::WinRT.IInspectable.Vftbl.AbiToProjectionVftable,
bool write_abi_interface_netstandard(writer& w, TypeDef const& type)
{
+ if (is_removed(type))
+ {
+ return false;
+ }
+
XLANG_ASSERT(get_category(type) == category::interface_type);
auto type_name = write_type_name_temp(w, type, "%", typedef_name_type::ABI);
auto nongenerics_class = w.write_temp("%_Delegates", bind(type, typedef_name_type::ABI, false));
@@ -7610,6 +7820,11 @@ private IObjectReference % => __% ?? Make__%();
void write_static_abi_classes(writer& w, TypeDef const& iface)
{
+ if (is_removed(iface))
+ {
+ return;
+ }
+
auto fast_abi_class_val = get_fast_abi_class_for_interface(iface);
if (fast_abi_class_val.has_value())
{
@@ -8084,6 +8299,11 @@ NativeMemory.Free((void*)abiToProjectionVftablePtr);
bool write_abi_interface(writer& w, TypeDef const& type)
{
+ if (is_removed(type))
+ {
+ return false;
+ }
+
bool is_generic = distance(type.GenericParam()) > 0;
XLANG_ASSERT(get_category(type) == category::interface_type);
auto type_name = write_type_name_temp(w, type, "%", typedef_name_type::ABI);
@@ -8642,6 +8862,11 @@ _defaultLazy = new Lazy<%>(() => GetDefaultReference<%.Vftbl>());
void write_class(writer& w, TypeDef const& type)
{
+ if (is_removed(type))
+ {
+ return;
+ }
+
writer::write_platform_guard guard{ w };
if (settings.component)
@@ -8898,6 +9123,11 @@ global::System.Collections.Concurrent.ConcurrentDictionary MarshalInterface<%
void write_delegate(writer& w, TypeDef const& type)
{
+ if (is_removed(type))
+ {
+ return;
+ }
+
if (settings.component)
{
write_authoring_metadata_type(w, type);
@@ -9025,6 +9265,11 @@ public static ObjectReferenceValue CreateMarshaler2(% obj) => MarshalInterface<%
void write_abi_delegate(writer& w, TypeDef const& type)
{
+ if (is_removed(type))
+ {
+ return;
+ }
+
auto method = get_delegate_invoke(type);
method_signature signature{ method };
auto type_name = write_type_name_temp(w, type);
@@ -9687,6 +9932,11 @@ return true;
void write_enum(writer& w, TypeDef const& type)
{
+ if (is_removed(type))
+ {
+ return;
+ }
+
if (settings.component)
{
write_authoring_metadata_type(w, type);
@@ -9713,6 +9963,11 @@ return true;
{
if (auto constant = field.Constant())
{
+ if (is_removed(field))
+ {
+ continue;
+ }
+ write_obsolete_attribute(w, field);
w.write("%% = unchecked((%)%),\n",
bind(field.CustomAttribute()),
field.Name(), enum_underlying_type, bind(constant));
@@ -9724,6 +9979,11 @@ return true;
void write_struct(writer& w, TypeDef const& type)
{
+ if (is_removed(type))
+ {
+ return;
+ }
+
if (settings.component)
{
write_authoring_metadata_type(w, type);
@@ -9821,6 +10081,11 @@ public override int GetHashCode() => %;
return;
}
+ if (is_removed(type))
+ {
+ return;
+ }
+
w.write("[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]\n% struct %\n{\n",
internal_accessibility(),
bind(type, typedef_name_type::ABI, false));
@@ -10139,6 +10404,10 @@ bind_list(", ", signature.params())
void write_factory_class(writer& w, TypeDef const& type)
{
+ if (is_removed(type))
+ {
+ return;
+ }
auto factory_type_name = write_type_name_temp(w, type, "%ServerActivationFactory", typedef_name_type::CCW);
auto is_activatable = !is_static(type) && has_default_constructor(type);
auto type_name = write_type_name_temp(w, type, "%", typedef_name_type::Projected);
@@ -11153,4 +11422,4 @@ return true;
//------------------------------------------------------------------------------
)", VERSION_STRING);
}
-}
\ No newline at end of file
+}
diff --git a/src/cswinrt/helpers.h b/src/cswinrt/helpers.h
index 9968b22ef1..d6bbe2b43c 100644
--- a/src/cswinrt/helpers.h
+++ b/src/cswinrt/helpers.h
@@ -34,6 +34,58 @@ namespace cswinrt
return has_attribute(prop, "Windows.Foundation.Metadata", "NoExceptionAttribute");
}
+ template
+ bool is_deprecated(T const& row)
+ {
+ return has_attribute(row, "Windows.Foundation.Metadata", "DeprecatedAttribute");
+ }
+
+ template
+ auto get_deprecated_message(T const& row)
+ {
+ auto attr = get_attribute(row, "Windows.Foundation.Metadata", "DeprecatedAttribute");
+ if (attr)
+ {
+ auto sig = attr.Value();
+ auto const& fixedArgs = sig.FixedArgs();
+ if (fixedArgs.size() >= 1)
+ {
+ auto const& elemSig = std::get(fixedArgs[0].value);
+ if (std::holds_alternative(elemSig.value))
+ {
+ return std::string(std::get(elemSig.value));
+ }
+ }
+ }
+ return std::string{};
+ }
+
+ template
+ bool is_removed(T const& row)
+ {
+ auto attr = get_attribute(row, "Windows.Foundation.Metadata", "DeprecatedAttribute");
+ if (!attr)
+ {
+ return false;
+ }
+ auto sig = attr.Value();
+ auto const& fixedArgs = sig.FixedArgs();
+ if (fixedArgs.size() >= 2)
+ {
+ // DeprecationType enum: Deprecate=0, Remove=1
+ auto const& elemSig = std::get(fixedArgs[1].value);
+ auto const& enumVal = std::get(elemSig.value);
+ return std::visit([](auto v) -> bool { return static_cast(v) == 1; }, enumVal.value);
+ }
+ return false;
+ }
+
+ template
+ bool is_deprecated_not_removed(T const& row)
+ {
+ return is_deprecated(row) && !is_removed(row);
+ }
+
bool is_exclusive_to(TypeDef const& type)
{
return get_category(type) == category::interface_type && has_attribute(type, "Windows.Foundation.Metadata"sv, "ExclusiveToAttribute"sv);
diff --git a/src/cswinrt/main.cpp b/src/cswinrt/main.cpp
index 0a2241eb10..2bb8032a41 100644
--- a/src/cswinrt/main.cpp
+++ b/src/cswinrt/main.cpp
@@ -164,6 +164,7 @@ Where is one or more of:
for (auto&& type : members.classes)
{
if (!settings.filter.includes(type)) { continue; }
+ if (is_removed(type)) { continue; }
for (auto&& attribute : type.CustomAttribute())
{
auto attribute_name = attribute.TypeNamespaceAndName();
diff --git a/tests/DeprecatedRemovedTest/.gitignore b/tests/DeprecatedRemovedTest/.gitignore
new file mode 100644
index 0000000000..a7ec8e02c7
--- /dev/null
+++ b/tests/DeprecatedRemovedTest/.gitignore
@@ -0,0 +1 @@
+tests/DeprecatedRemovedTest/output/
diff --git a/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl
new file mode 100644
index 0000000000..64fcd52048
--- /dev/null
+++ b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl
@@ -0,0 +1,189 @@
+// Test IDL for verifying deprecated and removed API support
+// Contains types with DeprecationType.Deprecate (0) and DeprecationType.Remove (1)
+//
+// This IDL covers ALL deprecation gaps found during functional testing:
+// - Type-level: enums, enum fields, structs, delegates, interfaces, classes
+// - Member-level on [default_interface] runtimeclass: methods, properties, events
+// - ABI code generation for removed types (must not reference missing projected types)
+// - MIDL attribute placement (DeprecatedAttribute on getter/add methods, not Property/Event rows)
+import "Windows.Foundation.idl";
+
+namespace DeprecatedRemovedTest
+{
+ // ========================================================================
+ // ENUMS
+ // ========================================================================
+
+ // Fully removed enum - should NOT appear in projection
+ [deprecated("RemovedEnum has been removed.", remove, 2)]
+ enum RemovedEnum
+ {
+ Alpha = 0,
+ Beta = 1
+ };
+
+ // Partially removed enum - Hidden value should be absent
+ enum PartiallyRemovedEnum
+ {
+ Visible = 0,
+ [deprecated("Hidden value has been removed.", remove, 2)]
+ Hidden = 1,
+ AlsoVisible = 2
+ };
+
+ // Deprecated enum - should have [Obsolete] but still be present
+ [deprecated("DeprecatedEnum is deprecated for testing.", deprecate, 1)]
+ enum DeprecatedEnum
+ {
+ First = 0,
+ Second = 1
+ };
+
+ // Normal enum for comparison
+ enum NormalEnum
+ {
+ One = 0,
+ Two = 1
+ };
+
+ // ========================================================================
+ // DELEGATES
+ // ========================================================================
+
+ // Deprecated delegate
+ [deprecated("DeprecatedDelegate is deprecated.", deprecate, 1)]
+ delegate void DeprecatedDelegate();
+
+ // Removed delegate - should NOT appear in projection
+ [deprecated("RemovedDelegate is gone.", remove, 2)]
+ delegate void RemovedDelegate();
+
+ // Normal delegate for comparison
+ delegate void NormalDelegate();
+
+ // Event handler delegate for event tests
+ delegate void TestEventHandler(String message);
+
+ // ========================================================================
+ // STRUCTS
+ // ========================================================================
+
+ // Deprecated struct
+ [deprecated("DeprecatedStruct is deprecated.", deprecate, 1)]
+ struct DeprecatedStruct
+ {
+ Int32 Value;
+ };
+
+ // Removed struct - should NOT appear in projection
+ [deprecated("RemovedStruct has been removed.", remove, 2)]
+ struct RemovedStruct
+ {
+ Int32 Value;
+ };
+
+ // ========================================================================
+ // INTERFACES
+ // ========================================================================
+
+ // Interface with mix of normal, deprecated, and removed methods
+ interface IVtableTest
+ {
+ void NormalMethod();
+ [deprecated("DeprecatedMethod is deprecated.", deprecate, 1)]
+ void DeprecatedMethod();
+ [deprecated("RemovedMethod is gone.", remove, 2)]
+ void RemovedMethod();
+ void AnotherNormalMethod();
+ };
+
+ // Deprecated interface - should have [Obsolete] but still be present
+ [deprecated("IDeprecatedInterface is deprecated.", deprecate, 1)]
+ interface IDeprecatedInterface
+ {
+ void DeprecatedWork();
+ };
+
+ // Removed interface - should NOT appear in projection at all
+ // (Previously caused CS0234 errors when ABI code referenced missing projected type)
+ [deprecated("IRemovedInterface has been removed.", remove, 2)]
+ interface IRemovedInterface
+ {
+ void RemovedWork();
+ };
+
+ // ========================================================================
+ // RUNTIMECLASSES
+ // ========================================================================
+
+ // Main test class with [default_interface] — tests member-level deprecation
+ // on the projected class (write_class_method, write_class_event, write_class_members)
+ [default_interface]
+ runtimeclass TestClass
+ {
+ TestClass();
+
+ [deprecated("Constructor with name is deprecated.", deprecate, 1)]
+ TestClass(String name);
+
+ [deprecated("Constructor with name and config has been removed.", remove, 2)]
+ TestClass(String name, Int32 config);
+
+ // --- Methods ---
+ void ActiveMethod();
+ [deprecated("RemovedMethod has been removed.", remove, 2)]
+ void RemovedMethod();
+ [deprecated("DeprecatedMethod is deprecated.", deprecate, 1)]
+ void DeprecatedMethod();
+ static void ActiveStaticMethod();
+ [deprecated("RemovedStaticMethod has been removed.", remove, 2)]
+ static void RemovedStaticMethod();
+
+ // --- Properties (Gap: MIDL puts DeprecatedAttr on getter, not Property row) ---
+ String NormalProp { get; };
+ [deprecated("DeprecatedProp is deprecated.", deprecate, 1)]
+ String DeprecatedProp { get; };
+ [deprecated("RemovedProp has been removed.", remove, 2)]
+ String RemovedProp { get; };
+
+ // --- Read-write properties ---
+ String WritableProp;
+ [deprecated("WritableDeprecatedProp is deprecated.", deprecate, 1)]
+ String WritableDeprecatedProp;
+ [deprecated("WritableRemovedProp has been removed.", remove, 2)]
+ String WritableRemovedProp;
+
+ // --- Static properties ---
+ static String StaticProp { get; };
+ [deprecated("StaticDeprecatedProp is deprecated.", deprecate, 1)]
+ static String StaticDeprecatedProp { get; };
+ [deprecated("StaticRemovedProp has been removed.", remove, 2)]
+ static String StaticRemovedProp { get; };
+
+ // --- Events (Gap: MIDL puts DeprecatedAttr on add method, not Event row) ---
+ event TestEventHandler NormalEvent;
+ [deprecated("DeprecatedEvent is deprecated.", deprecate, 1)]
+ event TestEventHandler DeprecatedEvent;
+ [deprecated("RemovedEvent has been removed.", remove, 2)]
+ event TestEventHandler RemovedEvent;
+ }
+
+ // Fully deprecated class
+ [deprecated("DeprecatedClass is deprecated.", deprecate, 1)]
+ [default_interface]
+ runtimeclass DeprecatedClass
+ {
+ DeprecatedClass();
+ void Method();
+ }
+
+ // Fully removed class - should NOT appear in projection
+ // (Previously caused CS0234 when ABI code referenced missing projected type)
+ [deprecated("RemovedClass has been removed.", remove, 2)]
+ [default_interface]
+ runtimeclass RemovedClass
+ {
+ RemovedClass();
+ void Method();
+ }
+}
diff --git a/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.winmd b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.winmd
new file mode 100644
index 0000000000..6f56ca4ac4
Binary files /dev/null and b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.winmd differ
diff --git a/tests/DeprecatedRemovedTest/run_test.ps1 b/tests/DeprecatedRemovedTest/run_test.ps1
new file mode 100644
index 0000000000..7ed0c27f9c
--- /dev/null
+++ b/tests/DeprecatedRemovedTest/run_test.ps1
@@ -0,0 +1,436 @@
+# End-to-end test for CSWinRT deprecated and removed API support
+# Compiles a test IDL with deprecated/removed types to WinMD,
+# generates C# projection with cswinrt.exe, and verifies the output.
+#
+# Regression coverage for gaps found during functional testing:
+# - Commit 4c180d3f: Member-level deprecation on [default_interface] runtimeclass
+# - Commit ed2ebda2: Type-level ABI code referencing removed projected types (CS0234)
+# - Commit b4ba8896: MIDL attribute on getter/add methods, not Property/Event rows
+#
+# Usage: pwsh -File run_test.ps1 [-CsWinRTExe path\to\cswinrt.exe]
+
+param(
+ [string]$CsWinRTExe = "_build\x64\Release\cswinrt\bin\cswinrt.exe"
+)
+
+$ErrorActionPreference = "Stop"
+$script:passed = 0
+$script:failed = 0
+
+function Test-Result {
+ param([bool]$Condition, [string]$Message)
+ if ($Condition) {
+ Write-Host " PASS: $Message" -ForegroundColor Green
+ $script:passed++
+ } else {
+ Write-Host " FAIL: $Message" -ForegroundColor Red
+ $script:failed++
+ }
+}
+
+$testDir = "tests\DeprecatedRemovedTest"
+$outputDir = "$testDir\output"
+$winmd = "$testDir\DeprecatedRemovedTest.winmd"
+$outputFile = "$outputDir\DeprecatedRemovedTest.cs"
+
+# ============================================================================
+# Step 1: Compile IDL to WinMD (if not already done)
+# ============================================================================
+Write-Host "`n=== Step 1: Compile IDL to WinMD ===" -ForegroundColor Cyan
+
+if (!(Test-Path $winmd)) {
+ $vcvarsall = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat"
+ $sdkUnion = "C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.26100.0"
+ $sdkIncWinrt = "C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\winrt"
+
+ $midlResult = cmd /c "`"$vcvarsall`" x64 >nul 2>&1 && midl /winrt /metadata_dir `"$sdkUnion`" /nomidl /W1 /nologo /I `"$sdkIncWinrt`" /h nul /dlldata nul /iid nul /proxy nul /notlb /winmd `"$winmd`" `"$testDir\DeprecatedRemovedTest.idl`"" 2>&1
+ if ($LASTEXITCODE -ne 0) {
+ Write-Host " ERROR: MIDL compilation failed" -ForegroundColor Red
+ $midlResult | ForEach-Object { Write-Host " $_" }
+ exit 1
+ }
+}
+Test-Result (Test-Path $winmd) "WinMD file exists"
+
+# ============================================================================
+# Step 2: Generate C# projection
+# ============================================================================
+Write-Host "`n=== Step 2: Generate C# projection ===" -ForegroundColor Cyan
+
+if (!(Test-Path $CsWinRTExe)) {
+ Write-Host " ERROR: cswinrt.exe not found at $CsWinRTExe" -ForegroundColor Red
+ Write-Host " Build CSWinRT first." -ForegroundColor Yellow
+ exit 1
+}
+
+New-Item -ItemType Directory -Force $outputDir | Out-Null
+& $CsWinRTExe -input $winmd -input local -include DeprecatedRemovedTest -output $outputDir 2>&1 | Out-Null
+Test-Result (Test-Path $outputFile) "C# projection file generated"
+
+$content = Get-Content $outputFile -Raw
+
+# ============================================================================
+# Step 3: Verify REMOVED types are excluded from projection
+# ============================================================================
+Write-Host "`n=== Step 3: Removed types excluded ===" -ForegroundColor Cyan
+
+# RemovedEnum - entire enum should not exist
+Test-Result ($content -notmatch 'enum RemovedEnum') `
+ "RemovedEnum type definition excluded"
+
+# RemovedStruct - entire struct should not exist in user namespace
+Test-Result ($content -notmatch '(?m)^\s*public struct RemovedStruct\b') `
+ "RemovedStruct not in user namespace"
+
+# RemovedClass - no user-facing class definition
+$hasRemovedClassDef = $content -match '(?m)(public|internal) (sealed )?class RemovedClass[^R]'
+Test-Result (-not $hasRemovedClassDef) `
+ "RemovedClass class definition excluded"
+
+# RemovedDelegate - no user-facing delegate declaration
+Test-Result ($content -notmatch 'delegate void RemovedDelegate\(\)') `
+ "RemovedDelegate declaration excluded"
+
+# IRemovedInterface - projected interface should NOT exist
+# (Regression: commit ed2ebda2 — previously generated empty interface with broken ABI reference)
+Test-Result ($content -notmatch 'interface IRemovedInterface') `
+ "IRemovedInterface projected interface excluded"
+
+# ============================================================================
+# Step 4: Verify DEPRECATED types have [Obsolete] annotations
+# ============================================================================
+Write-Host "`n=== Step 4: Deprecated types have [Obsolete] ===" -ForegroundColor Cyan
+
+Test-Result ($content -match 'Obsolete.*DeprecatedEnum is deprecated') `
+ "DeprecatedEnum has [Obsolete] with message"
+
+Test-Result ($content -match 'Obsolete.*DeprecatedClass is deprecated') `
+ "DeprecatedClass has [Obsolete] with message"
+
+Test-Result ($content -match 'Obsolete.*DeprecatedDelegate is deprecated') `
+ "DeprecatedDelegate has [Obsolete] with message"
+
+Test-Result ($content -match 'Obsolete.*DeprecatedStruct is deprecated') `
+ "DeprecatedStruct has [Obsolete] with message"
+
+Test-Result ($content -match 'Obsolete.*IDeprecatedInterface is deprecated') `
+ "IDeprecatedInterface has [Obsolete] with message"
+
+# ============================================================================
+# Step 5: Verify NORMAL types are present without [Obsolete]
+# ============================================================================
+Write-Host "`n=== Step 5: Normal types present ===" -ForegroundColor Cyan
+
+Test-Result ($content -match 'enum NormalEnum') `
+ "NormalEnum type is present"
+
+Test-Result ($content -match 'delegate void NormalDelegate\(\)') `
+ "NormalDelegate declaration present"
+
+Test-Result ($content -match 'class TestClass\b') `
+ "TestClass class is present"
+
+# ============================================================================
+# Step 6: PartiallyRemovedEnum - removed field excluded, others present
+# ============================================================================
+Write-Host "`n=== Step 6: PartiallyRemovedEnum field removal ===" -ForegroundColor Cyan
+
+Test-Result ($content -match 'Visible =') `
+ "PartiallyRemovedEnum.Visible is present"
+
+Test-Result ($content -match 'AlsoVisible =') `
+ "PartiallyRemovedEnum.AlsoVisible is present"
+
+Test-Result ($content -notmatch 'Hidden =') `
+ "PartiallyRemovedEnum.Hidden is excluded (removed)"
+
+# ============================================================================
+# Step 7: IVtableTest - vtable slots preserved, removed method hidden
+# ============================================================================
+Write-Host "`n=== Step 7: IVtableTest vtable preservation ===" -ForegroundColor Cyan
+
+Test-Result ($content -match 'NormalMethod') `
+ "NormalMethod accessible in projection"
+
+Test-Result ($content -match 'AnotherNormalMethod') `
+ "AnotherNormalMethod accessible in projection"
+
+Test-Result ($content -match 'Obsolete.*DeprecatedMethod is deprecated') `
+ "DeprecatedMethod has [Obsolete] annotation"
+
+# ============================================================================
+# Step 8: TestClass method-level removal and deprecation
+# ============================================================================
+Write-Host "`n=== Step 8: TestClass method-level removal ===" -ForegroundColor Cyan
+
+# Removed methods should NOT have [Obsolete] - they're completely hidden from user API
+Test-Result ($content -notmatch 'Obsolete.*RemovedMethod has been removed') `
+ "Removed methods do NOT get [Obsolete] (fully hidden)"
+
+Test-Result ($content -match 'Obsolete.*DeprecatedMethod is deprecated') `
+ "TestClass.DeprecatedMethod has [Obsolete]"
+
+# ============================================================================
+# Step 9: TestClass PROPERTY deprecation/removal
+# (Regression: commit b4ba8896 — MIDL puts DeprecatedAttr on getter, not Property row)
+# ============================================================================
+Write-Host "`n=== Step 9: TestClass property deprecation/removal ===" -ForegroundColor Cyan
+
+# NormalProp should be present on the projected class
+Test-Result ($content -match '(?m)public string NormalProp\b') `
+ "NormalProp is present on projected TestClass"
+
+# DeprecatedProp should have [Obsolete] on the projected class
+Test-Result ($content -match 'Obsolete.*DeprecatedProp is deprecated') `
+ "DeprecatedProp has [Obsolete] on projected TestClass"
+
+# RemovedProp should NOT be on the projected class
+# (Regression: previously not removed because is_removed() checked Property row, not getter)
+$hasRemovedPropOnClass = $content -match '(?m)public string RemovedProp\b'
+Test-Result (-not $hasRemovedPropOnClass) `
+ "RemovedProp excluded from projected TestClass"
+
+# ============================================================================
+# Step 10: TestClass EVENT deprecation/removal
+# (Regression: commit b4ba8896 — MIDL puts DeprecatedAttr on add method, not Event row)
+# ============================================================================
+Write-Host "`n=== Step 10: TestClass event deprecation/removal ===" -ForegroundColor Cyan
+
+# NormalEvent should be present on the projected class
+Test-Result ($content -match '(?m)public event.*NormalEvent\b') `
+ "NormalEvent is present on projected TestClass"
+
+# DeprecatedEvent should have [Obsolete] on the projected class
+Test-Result ($content -match 'Obsolete.*DeprecatedEvent is deprecated') `
+ "DeprecatedEvent has [Obsolete] on projected TestClass"
+
+# RemovedEvent should NOT be on the projected class
+$hasRemovedEventOnClass = $content -match '(?m)public event.*RemovedEvent\b'
+Test-Result (-not $hasRemovedEventOnClass) `
+ "RemovedEvent excluded from projected TestClass"
+
+# ============================================================================
+# Step 11: Interface member signatures (ITestClass)
+# (Regression: commit 4c180d3f — write_interface_member_signatures not filtering)
+# ============================================================================
+Write-Host "`n=== Step 11: Interface member signatures ===" -ForegroundColor Cyan
+
+# Extract internal interface ITestClass body using brace-counting (avoid early match on { get; })
+$lines = $content -split "`n"
+$inIface = $false; $depth = 0; $ifaceLines = @()
+foreach ($line in $lines) {
+ if ($line -match 'internal interface ITestClass\b') { $inIface = $true; $depth = 0 }
+ if ($inIface) {
+ $depth += ([regex]::Matches($line, '\{')).Count
+ $depth -= ([regex]::Matches($line, '\}')).Count
+ $ifaceLines += $line
+ if ($depth -le 0 -and $ifaceLines.Count -gt 1) { break }
+ }
+}
+$ifaceBody = $ifaceLines -join "`n"
+if ($ifaceBody) {
+
+ Test-Result ($ifaceBody -match 'void ActiveMethod\(\)') `
+ "ITestClass interface has ActiveMethod"
+
+ Test-Result ($ifaceBody -notmatch 'void RemovedMethod\(\)') `
+ "ITestClass interface excludes RemovedMethod"
+
+ Test-Result ($ifaceBody -notmatch 'RemovedProp') `
+ "ITestClass interface excludes RemovedProp"
+
+ Test-Result ($ifaceBody -notmatch 'RemovedEvent') `
+ "ITestClass interface excludes RemovedEvent"
+
+ Test-Result ($ifaceBody -match 'Obsolete.*DeprecatedMethod') `
+ "ITestClass interface has [Obsolete] on DeprecatedMethod"
+
+ Test-Result ($ifaceBody -match 'Obsolete.*DeprecatedProp') `
+ "ITestClass interface has [Obsolete] on DeprecatedProp"
+
+ Test-Result ($ifaceBody -match 'Obsolete.*DeprecatedEvent') `
+ "ITestClass interface has [Obsolete] on DeprecatedEvent"
+} else {
+ Write-Host " FAIL: Could not find internal interface ITestClass" -ForegroundColor Red
+ $script:failed += 7
+}
+
+# ============================================================================
+# Step 12: ABI code integrity for removed types
+# (Regression: commit ed2ebda2 — ABI code referenced removed projected types)
+# ============================================================================
+Write-Host "`n=== Step 12: ABI code integrity for removed types ===" -ForegroundColor Cyan
+
+# ABI namespace should NOT reference IRemovedClass or IRemovedInterface as helper types
+# These would cause CS0234 if generated because the projected types don't exist
+Test-Result ($content -notmatch 'WindowsRuntimeHelperType.*IRemovedClass') `
+ "No WindowsRuntimeHelperType reference to IRemovedClass"
+
+Test-Result ($content -notmatch 'WindowsRuntimeHelperType.*IRemovedInterface') `
+ "No WindowsRuntimeHelperType reference to IRemovedInterface"
+
+# No RcwFactoryAttribute for removed class
+Test-Result ($content -notmatch 'RemovedClassRcwFactoryAttribute') `
+ "No RcwFactoryAttribute for RemovedClass"
+
+# ============================================================================
+# Step 13: Read-write property deprecation/removal
+# ============================================================================
+Write-Host "`n=== Step 13: Read-write property deprecation/removal ===" -ForegroundColor Cyan
+
+Test-Result ($content -match '(?m)public string WritableProp\b') `
+ "WritableProp is present on projected TestClass"
+
+Test-Result ($content -match 'Obsolete.*WritableDeprecatedProp is deprecated') `
+ "WritableDeprecatedProp has [Obsolete] on projected TestClass"
+
+$hasWritableRemovedProp = $content -match '(?m)public string WritableRemovedProp\b'
+Test-Result (-not $hasWritableRemovedProp) `
+ "WritableRemovedProp excluded from projected TestClass"
+
+# Check setter exists for writable prop
+Test-Result ($content -match 'set_WritableProp') `
+ "WritableProp setter path exists in ABI"
+
+# ============================================================================
+# Step 14: Static property deprecation/removal
+# ============================================================================
+Write-Host "`n=== Step 14: Static property deprecation/removal ===" -ForegroundColor Cyan
+
+Test-Result ($content -match '(?m)(public )?static string StaticProp\b') `
+ "StaticProp is present on projected TestClass"
+
+Test-Result ($content -match 'Obsolete.*StaticDeprecatedProp is deprecated') `
+ "StaticDeprecatedProp has [Obsolete] on projected TestClass"
+
+$hasStaticRemovedProp = $content -match '(?m)(public )?static string StaticRemovedProp\b'
+Test-Result (-not $hasStaticRemovedProp) `
+ "StaticRemovedProp excluded from projected TestClass"
+
+# ============================================================================
+# Step 15: Constructor deprecation/removal
+# ============================================================================
+Write-Host "`n=== Step 15: Constructor deprecation/removal ===" -ForegroundColor Cyan
+
+# Default constructor should be present
+Test-Result ($content -match 'TestClass\(\)') `
+ "Default constructor TestClass() is present"
+
+# Deprecated constructor should have [Obsolete]
+Test-Result ($content -match 'Obsolete.*Constructor with name is deprecated') `
+ "Deprecated constructor has [Obsolete] annotation"
+
+# Removed constructor should NOT be present in user-facing code
+$hasRemovedCtor = $content -match 'Obsolete.*Constructor with name and config has been removed'
+$hasRemovedCtorDef = $content -match 'TestClass\(string\s+\w+,\s*int\s+\w+\)'
+Test-Result (-not $hasRemovedCtor -and -not $hasRemovedCtorDef) `
+ "Removed constructor excluded from projected TestClass"
+
+# ============================================================================
+# Step 16: Interface checks for new constructs
+# ============================================================================
+Write-Host "`n=== Step 16: Interface checks for new constructs ===" -ForegroundColor Cyan
+
+if ($ifaceBody) {
+ Test-Result ($ifaceBody -notmatch 'WritableRemovedProp') `
+ "ITestClass interface excludes WritableRemovedProp"
+
+ Test-Result ($ifaceBody -match 'Obsolete.*WritableDeprecatedProp') `
+ "ITestClass interface has [Obsolete] on WritableDeprecatedProp"
+
+ Test-Result ($ifaceBody -match 'WritableProp') `
+ "ITestClass interface has WritableProp"
+} else {
+ Write-Host " SKIP: ITestClass not found for new construct checks" -ForegroundColor Yellow
+}
+
+# ============================================================================
+# Step 17: Compile generated C# code
+# (Ultimate regression test — catches any ABI reference to missing types)
+# ============================================================================
+Write-Host "`n=== Step 17: Compile generated C# code ===" -ForegroundColor Cyan
+
+$compileDir = "$testDir\compile_test"
+if (Test-Path $compileDir) { Remove-Item $compileDir -Recurse -Force }
+New-Item -ItemType Directory -Force $compileDir | Out-Null
+
+# Create a minimal .csproj that compiles the generated code
+$csproj = @"
+
+
+ net8.0
+ Library
+ true
+ false
+ false
+ CS0618;CS0612
+
+
+
+
+
+
+ ..\..\..\src\WinRT.Runtime\bin\Release\net8.0\WinRT.Runtime.dll
+
+
+
+"@
+$csproj | Set-Content "$compileDir\CompileTest.csproj"
+
+$buildResult = dotnet build "$compileDir\CompileTest.csproj" --nologo 2>&1
+$buildOutput = $buildResult | Out-String
+$buildSuccess = $buildOutput -match 'Build succeeded'
+$hasErrors = $buildOutput -match 'error CS'
+
+Test-Result $buildSuccess `
+ "Generated C# code compiles without errors"
+
+if (-not $buildSuccess -and $hasErrors) {
+ # Show the specific errors
+ $buildResult | Select-String "error CS" | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
+}
+
+# Cleanup compile test
+Remove-Item $compileDir -Recurse -Force -ErrorAction SilentlyContinue
+
+# ============================================================================
+# Step 18: Component generation excludes removed classes
+# ============================================================================
+Write-Host "`n=== Step 18: Component activation factory excludes removed classes ===" -ForegroundColor Cyan
+
+$componentDir = "$testDir\component_output"
+New-Item -ItemType Directory -Force $componentDir | Out-Null
+& $CsWinRTExe -input $winmd -input local -include DeprecatedRemovedTest -output $componentDir -component 2>&1 | Out-Null
+
+$moduleFile = "$componentDir\WinRT_Module.cs"
+Test-Result (Test-Path $moduleFile) `
+ "WinRT_Module.cs generated in component mode"
+
+if (Test-Path $moduleFile) {
+ $moduleContent = Get-Content $moduleFile -Raw
+
+ # RemovedClass must NOT appear in the activation factory
+ Test-Result ($moduleContent -notmatch 'RemovedClass') `
+ "RemovedClass excluded from component activation factory"
+
+ # Normal and deprecated classes MUST still appear
+ Test-Result ($moduleContent -match 'TestClass') `
+ "TestClass present in component activation factory"
+ Test-Result ($moduleContent -match 'DeprecatedClass') `
+ "DeprecatedClass present in component activation factory"
+}
+
+# Cleanup component output
+Remove-Item $componentDir -Recurse -Force -ErrorAction SilentlyContinue
+
+# ============================================================================
+# Summary
+# ============================================================================
+Write-Host "`n=== Summary ===" -ForegroundColor Cyan
+Write-Host "Passed: $($script:passed)" -ForegroundColor Green
+if ($script:failed -gt 0) {
+ Write-Host "Failed: $($script:failed)" -ForegroundColor Red
+ exit 1
+} else {
+ Write-Host "All checks passed!" -ForegroundColor Green
+}
diff --git a/tests/verify_removed.ps1 b/tests/verify_removed.ps1
new file mode 100644
index 0000000000..9a5139b93a
--- /dev/null
+++ b/tests/verify_removed.ps1
@@ -0,0 +1,91 @@
+# Verify deprecated and removed API support in CSWinRT
+# Validates helpers, code generation, and generated output
+
+$ErrorActionPreference = "Stop"
+$script:passed = 0
+$script:failed = 0
+
+function Assert-Contains {
+ param([string]$File, [string]$Pattern, [string]$Message)
+ $content = Get-Content $File -Raw
+ if ($content -match $Pattern) {
+ Write-Host " PASS: $Message" -ForegroundColor Green
+ $script:passed++
+ } else {
+ Write-Host " FAIL: $Message" -ForegroundColor Red
+ Write-Host " Expected pattern: $Pattern" -ForegroundColor Yellow
+ $script:failed++
+ }
+}
+
+Write-Host "`n=== helpers.h: deprecated/removed helpers ===" -ForegroundColor Cyan
+
+$helpersFile = "src\cswinrt\helpers.h"
+Assert-Contains $helpersFile "is_removed" "is_removed() function exists"
+Assert-Contains $helpersFile "is_deprecated" "is_deprecated() function exists"
+Assert-Contains $helpersFile "get_deprecated_message" "get_deprecated_message() function exists"
+Assert-Contains $helpersFile "is_deprecated_not_removed" "is_deprecated_not_removed() helper exists"
+Assert-Contains $helpersFile "DeprecatedAttribute" "References DeprecatedAttribute"
+
+Write-Host "`n=== code_writers.h: deprecated/removed handling ===" -ForegroundColor Cyan
+
+$writersFile = "src\cswinrt\code_writers.h"
+Assert-Contains $writersFile "is_removed" "code_writers.h has is_removed() checks"
+Assert-Contains $writersFile "write_obsolete_attribute" "code_writers.h has write_obsolete_attribute"
+Assert-Contains $writersFile "System.Obsolete" "Generates [System.Obsolete] annotations"
+
+Write-Host "`n=== Vtable preservation: write_vtable has NO is_removed checks ===" -ForegroundColor Cyan
+
+# The write_vtable function must iterate ALL methods including removed ones.
+# Verify it does NOT contain is_removed checks (vtable slots must be preserved).
+$writersContent = Get-Content $writersFile -Raw
+$vtableSection = [regex]::Match($writersContent, 'void write_vtable\(writer.*?\n\s{4}\}', [System.Text.RegularExpressions.RegexOptions]::Singleline).Value
+if ($vtableSection -and $vtableSection -notmatch 'is_removed') {
+ Write-Host " PASS: write_vtable does NOT skip removed methods (vtable preserved)" -ForegroundColor Green
+ $script:passed++
+} elseif (!$vtableSection) {
+ Write-Host " SKIP: Could not isolate write_vtable function" -ForegroundColor Yellow
+} else {
+ Write-Host " FAIL: write_vtable contains is_removed check (vtable slots may be broken)" -ForegroundColor Red
+ $script:failed++
+}
+
+# Verify write_interface_members DOES have is_removed checks (user projection hidden)
+$membersSection = [regex]::Match($writersContent, 'void write_interface_members\(writer.*?(?=\n\s{4}void\s)', [System.Text.RegularExpressions.RegexOptions]::Singleline).Value
+if ($membersSection -and $membersSection -match 'is_removed') {
+ Write-Host " PASS: write_interface_members skips removed methods (user API hidden)" -ForegroundColor Green
+ $script:passed++
+} else {
+ Write-Host " FAIL: write_interface_members missing is_removed check" -ForegroundColor Red
+ $script:failed++
+}
+
+Write-Host "`n=== Generated output verification ===" -ForegroundColor Cyan
+
+$cswinrtExe = "_build\x64\Release\cswinrt\bin\cswinrt.exe"
+if (Test-Path $cswinrtExe) {
+ $testDir = "_test_verify"
+ New-Item -ItemType Directory -Force $testDir | Out-Null
+ & $cswinrtExe -input local -include Windows.Media.PlayTo -output $testDir 2>&1 | Out-Null
+ $outputFile = Join-Path $testDir "Windows.Media.PlayTo.cs"
+ if (Test-Path $outputFile) {
+ Assert-Contains $outputFile "System.Obsolete" "[Obsolete] annotations present in generated C#"
+ Assert-Contains $outputFile "PlayToConnection may be altered" "Deprecation message preserved in [Obsolete]"
+ Assert-Contains $outputFile "enum PlayToConnectionState" "Deprecated enum type generated"
+ Assert-Contains $outputFile "Disconnected" "Deprecated enum values generated"
+ } else {
+ Write-Host " SKIP: Generated file not found" -ForegroundColor Yellow
+ }
+ Remove-Item $testDir -Recurse -Force -ErrorAction SilentlyContinue
+} else {
+ Write-Host " SKIP: cswinrt.exe not built" -ForegroundColor Yellow
+}
+
+Write-Host "`n=== Summary ===" -ForegroundColor Cyan
+Write-Host "Passed: $($script:passed)" -ForegroundColor Green
+if ($script:failed -gt 0) {
+ Write-Host "Failed: $($script:failed)" -ForegroundColor Red
+ exit 1
+} else {
+ Write-Host "All checks passed!" -ForegroundColor Green
+}