diff --git a/.gitignore b/.gitignore index 9493fdad5..3bcaf349c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ Release Generated Files obj vsix/LICENSE + +_build/ diff --git a/cppwinrt/code_writers.h b/cppwinrt/code_writers.h index 35e9935d0..a3ffebc52 100644 --- a/cppwinrt/code_writers.h +++ b/cppwinrt/code_writers.h @@ -195,26 +195,97 @@ namespace cppwinrt return { w, write_close_namespace }; } - static void write_enum_field(writer& w, Field const& field) + template + static void write_deprecated_impl(writer& w, T const& row) { - auto format = R"( % = %, -)"; + if (auto attr = get_attribute(row, "Windows.Foundation.Metadata", "DeprecatedAttribute")) + { + auto message = get_attribute_value(attr, 0); + w.write("[[deprecated(\"%\")]] ", message); + } + } + + static void write_deprecated_method(writer& w, MethodDef const& method) + { + write_deprecated_impl(w, method); + } + + static void write_deprecated_field(writer& w, Field const& field) + { + write_deprecated_impl(w, field); + } + + static void write_deprecated_redeclaration(writer& w, TypeDef const& type) + { + if (is_removed(type)) + { + return; + } + + if (auto attr = get_attribute(type, "Windows.Foundation.Metadata", "DeprecatedAttribute")) + { + auto message = get_attribute_value(attr, 0); + w.write(" struct [[deprecated(\"%\")]] %;\n", message, type.TypeName()); + } + } + + static void write_enum_field(writer& w, Field const& field, bool parent_deprecated, std::string_view parent_deprecated_message) + { + if (is_removed(field)) + { + return; + } if (auto constant = field.Constant()) { - w.write(format, field.Name(), *constant); + if (is_deprecated(field)) + { + w.write(" % % = %,\n", field.Name(), bind(field), *constant); + } + else if (parent_deprecated) + { + w.write(" % [[deprecated(\"%\")]] = %,\n", field.Name(), parent_deprecated_message, *constant); + } + else + { + auto format = R"( % = %, +)"; + w.write(format, field.Name(), *constant); + } } } static void write_enum(writer& w, TypeDef const& type) { + if (is_removed(type)) + { + return; + } + + bool type_deprecated = is_deprecated(type); + std::string_view deprecated_message; + if (type_deprecated) + { + deprecated_message = get_deprecated_message(type); + } + auto format = R"( enum class % : % { % }; )"; auto fields = type.FieldList(); - w.write(format, type.TypeName(), fields.first.Signature().Type(), bind_each(fields)); + + w.write(format, + type.TypeName(), + fields.first.Signature().Type(), + [&](writer& w) + { + for (auto&& field : fields) + { + write_enum_field(w, field, type_deprecated, deprecated_message); + } + }); } static void write_enum_operators(writer& w, TypeDef const& type) @@ -306,6 +377,7 @@ namespace cppwinrt if (get_category(type) == category::enum_type) { + if (is_removed(type)) return; auto format = R"( enum class % : %; )"; @@ -392,6 +464,7 @@ namespace cppwinrt static void write_category(writer& w, TypeDef const& type, std::string_view const& category) { + if (is_removed(type)) return; auto generics = type.GenericParam(); if (empty(generics)) @@ -433,6 +506,7 @@ namespace cppwinrt static void write_name(writer& w, TypeDef const& type) { + if (is_removed(type)) return; type_name type_name(type); auto generics = type.GenericParam(); @@ -459,6 +533,7 @@ namespace cppwinrt static void write_guid(writer& w, TypeDef const& type) { + if (is_removed(type)) return; auto attribute = get_attribute(type, "Windows.Foundation.Metadata", "GuidAttribute"); if (!attribute) @@ -498,6 +573,7 @@ namespace cppwinrt static void write_default_interface(writer& w, TypeDef const& type) { + if (is_removed(type)) return; if (auto default_interface = get_default_interface(type)) { auto format = R"( template <> struct default_interface<%>{ using type = %; }; @@ -508,6 +584,7 @@ namespace cppwinrt static void write_struct_category(writer& w, TypeDef const& type) { + if (is_removed(type)) return; auto format = R"( template <> struct category<%>{ using type = struct_category<%>; }; )"; @@ -985,12 +1062,18 @@ namespace cppwinrt static void write_consume_declaration(writer& w, MethodDef const& method) { + if (is_removed(method)) + { + return; + } + method_signature signature{ method }; auto async_types_guard = w.push_async_types(signature.is_async()); auto method_name = get_name(method); auto type = method.Parent(); - w.write(" %auto %(%) const%;\n", + w.write(" %%auto %(%) const%;\n", + bind(method), is_get_overload(method) ? "[[nodiscard]] " : "", method_name, bind(signature), @@ -999,7 +1082,7 @@ namespace cppwinrt if (is_add_overload(method)) { auto format = R"( using %_revoker = impl::event_revoker<%, &impl::abi_t<%>::remove_%>; - [[nodiscard]] auto %(auto_revoke_t, %) const; + %[[nodiscard]] auto %(auto_revoke_t, %) const; )"; w.write(format, @@ -1007,6 +1090,7 @@ namespace cppwinrt type, type, method_name, + bind(method), method_name, bind(signature)); } @@ -1118,6 +1202,11 @@ namespace cppwinrt static void write_consume_definition(writer& w, TypeDef const& type, MethodDef const& method, std::pair const& generics, std::string_view const& type_impl_name) { + if (is_removed(method)) + { + return; + } + auto method_name = get_name(method); method_signature signature{ method }; auto async_types_guard = w.push_async_types(signature.is_async()); @@ -2579,6 +2668,11 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable static void write_delegate(writer& w, TypeDef const& type) { + if (is_removed(type)) + { + return; + } + auto generics = type.GenericParam(); auto guard{ w.push_generic_params(generics) }; auto type_name = type.TypeName(); @@ -2627,6 +2721,11 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable static void write_delegate_implementation(writer& w, TypeDef const& type) { + if (is_removed(type)) + { + return; + } + auto format = R"( template struct delegate<%, H> final : implements_delegate<%, H> { delegate(H&& handler) : implements_delegate<%, H>(std::forward(handler)) {} @@ -2656,6 +2755,11 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable static void write_delegate_definition(writer& w, TypeDef const& type) { + if (is_removed(type)) + { + return; + } + auto generics = type.GenericParam(); auto guard{ w.push_generic_params(generics) }; auto type_name = type.TypeName(); @@ -2894,7 +2998,10 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable for (auto&& type : types) { - structs.emplace_back(w, type); + if (!is_removed(type)) + { + structs.emplace_back(w, type); + } } auto depends = [](writer& w, complex_struct const& left, complex_struct const& right) @@ -3101,9 +3208,15 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable { for (auto&& method : factory.type.MethodList()) { + if (is_removed(method)) + { + continue; + } + method_signature signature{ method }; - w.write(" %%(%);\n", + w.write(" %%%(%);\n", + bind(method), signature.params().size() == 1 ? "explicit " : "", type_name, bind(signature)); @@ -3114,11 +3227,17 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable { for (auto&& method : factory.type.MethodList()) { + if (is_removed(method)) + { + continue; + } + method_signature signature{ method }; auto& params = signature.params(); params.resize(params.size() - 2); - w.write(" %%(%);\n", + w.write(" %%%(%);\n", + bind(method), signature.params().size() == 1 ? "explicit " : "", type_name, bind(signature)); @@ -3129,6 +3248,11 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable static void write_constructor_definition(writer& w, MethodDef const& method, TypeDef const& type, TypeDef const& factory) { + if (is_removed(method)) + { + return; + } + auto type_name = type.TypeName(); method_signature signature{ method }; @@ -3148,6 +3272,11 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable static void write_composable_constructor_definition(writer& w, MethodDef const& method, TypeDef const& type, TypeDef const& factory) { + if (is_removed(method)) + { + return; + } + auto type_name = type.TypeName(); method_signature signature{ method }; auto& params = signature.params(); @@ -3189,13 +3318,18 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable for (auto&& method : factory.second.type.MethodList()) { - method_signature signature{ method }; - auto method_name = get_name(method); + if (is_removed(method)) + { + continue; + } + + method_signature signature{ method }; auto method_name = get_name(method); auto async_types_guard = w.push_async_types(signature.is_async()); if (is_opt_type) { - w.write(" %static % %(%);\n", + w.write(" %%static % %(%);\n", + bind(method), is_get_overload(method) ? "[[nodiscard]] " : "", signature.return_signature(), method_name, @@ -3203,7 +3337,8 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable } else { - w.write(" %static auto %(%);\n", + w.write(" %%static auto %(%);\n", + bind(method), is_get_overload(method) ? "[[nodiscard]] " : "", method_name, bind(signature)); @@ -3223,18 +3358,20 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable if (is_opt_type) { - auto format = R"( [[nodiscard]] static %_revoker %(auto_revoke_t, %); + auto format = R"( %[[nodiscard]] static %_revoker %(auto_revoke_t, %); )"; w.write(format, + bind(method), method_name, method_name, bind(signature)); } else { - auto format = R"( [[nodiscard]] static auto %(auto_revoke_t, %); + auto format = R"( %[[nodiscard]] static auto %(auto_revoke_t, %); )"; w.write(format, + bind(method), method_name, bind(signature)); } @@ -3244,6 +3381,11 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable static void write_static_definitions(writer& w, MethodDef const& method, TypeDef const& type, TypeDef const& factory) { + if (is_removed(method)) + { + return; + } + auto type_name = type.TypeName(); method_signature signature{ method }; auto method_name = get_name(method); @@ -3288,6 +3430,11 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable static void write_class_definitions(writer& w, TypeDef const& type) { + if (is_removed(type)) + { + return; + } + if (settings.component_opt && settings.component_filter.includes(type)) { return; @@ -3412,6 +3559,11 @@ struct WINRT_IMPL_EMPTY_BASES produce_dispatch_to_overridable static void write_class(writer& w, TypeDef const& type) { + if (is_removed(type)) + { + return; + } + if (auto default_interface = get_default_interface(type)) { if (has_fastabi(type)) diff --git a/cppwinrt/component_writers.h b/cppwinrt/component_writers.h index cb748917d..7b5a745b2 100644 --- a/cppwinrt/component_writers.h +++ b/cppwinrt/component_writers.h @@ -71,7 +71,7 @@ namespace cppwinrt static void write_component_include(writer& w, TypeDef const& type) { - if (!has_factory_members(w, type) || is_always_disabled(type)) + if (!has_factory_members(w, type) || is_always_disabled(type) || is_removed(type)) { return; } @@ -94,7 +94,7 @@ namespace cppwinrt static void write_component_activation(writer& w, TypeDef const& type) { - if (!has_factory_members(w, type) || is_always_disabled(type)) + if (!has_factory_members(w, type) || is_always_disabled(type) || is_removed(type)) { return; } @@ -427,6 +427,11 @@ catch (...) { return winrt::to_hresult(); } { for (auto&& method : factory.type.MethodList()) { + if (is_removed(method)) + { + continue; + } + method_signature signature{ method }; auto format = R"( %::%(%) : @@ -450,6 +455,11 @@ catch (...) { return winrt::to_hresult(); } { for (auto&& method : factory.type.MethodList()) { + if (is_removed(method)) + { + continue; + } + method_signature signature{ method }; auto& params = signature.params(); params.resize(params.size() - 2); @@ -474,6 +484,11 @@ catch (...) { return winrt::to_hresult(); } { for (auto&& method : factory.type.MethodList()) { + if (is_removed(method)) + { + continue; + } + method_signature signature{ method }; auto method_name = get_name(method); w.async_types = signature.is_async(); diff --git a/cppwinrt/file_writers.h b/cppwinrt/file_writers.h index ed9386b4e..a52bd7358 100644 --- a/cppwinrt/file_writers.h +++ b/cppwinrt/file_writers.h @@ -181,6 +181,10 @@ namespace cppwinrt writer w; w.type_namespace = ns; + // Suppress C4996 (deprecated) in internal projection implementation code. + // The deprecated redeclarations below (outside the suppression) trigger warnings in user code. + w.write("#pragma warning(push)\n#pragma warning(disable: 4996)\n"); + { auto wrap_impl = wrap_impl_namespace(w); w.write_each(members.interfaces); @@ -213,6 +217,16 @@ namespace cppwinrt } } + w.write("#pragma warning(pop)\n"); + + { + auto wrap_type = wrap_type_namespace(w, ns); + w.write_each(members.interfaces); + w.write_each(members.classes); + w.write_each(members.delegates); + w.write_each(members.structs); + } + write_namespace_special(w, ns); write_close_file_guard(w); diff --git a/cppwinrt/helpers.h b/cppwinrt/helpers.h index 522d0bcd1..16557cb44 100644 --- a/cppwinrt/helpers.h +++ b/cppwinrt/helpers.h @@ -242,6 +242,39 @@ namespace cppwinrt return is_remove_overload(method) || has_attribute(method, "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"); + return get_attribute_value(attr, 0); + } + + template + bool is_removed(T const& row) + { + auto attr = get_attribute(row, "Windows.Foundation.Metadata", "DeprecatedAttribute"); + if (!attr) + { + return false; + } + auto args = attr.Value().FixedArgs(); + if (args.size() >= 2) + { + // DeprecationType enum: Deprecate=0, Remove=1 + auto val = std::get(args[1].value); + auto enum_val = std::get(val.value); + // Compare the underlying integer value (1 == Remove) + return std::visit([](auto v) -> bool { return static_cast(v) == 1; }, enum_val.value); + } + return false; + } + static bool has_fastabi(TypeDef const& type) { return settings.fastabi&& has_attribute(type, "Windows.Foundation.Metadata", "FastAbiAttribute"); diff --git a/cppwinrt/main.cpp b/cppwinrt/main.cpp index 70a55b076..f908383d8 100644 --- a/cppwinrt/main.cpp +++ b/cppwinrt/main.cpp @@ -394,6 +394,10 @@ R"( local Local ^%WinDir^%\System32\WinMetadata folder for (auto&& type : classes) { + if (is_removed(type)) + { + continue; + } write_component_g_h(type); write_component_g_cpp(type); write_component_h(type); diff --git a/docs/deprecated.md b/docs/deprecated.md new file mode 100644 index 000000000..ca94dbb5f --- /dev/null +++ b/docs/deprecated.md @@ -0,0 +1,63 @@ +# Deprecated Attribute Support + +C++/WinRT projects the `Windows.Foundation.Metadata.DeprecatedAttribute` from WinRT metadata as the C++ standard `[[deprecated("message")]]` attribute on generated types and members. + +## What Gets Annotated + +When a WinRT type or member is marked with `DeprecatedAttribute` in its metadata (`.winmd`), the generated C++/WinRT projection header will include the corresponding `[[deprecated]]` annotation: + +- **Enum types**: `[[deprecated("...")]] enum class MyEnum : int32_t { ... };` +- **Enum values**: Individual enumerators marked deprecated +- **Methods**: `[[deprecated("...")]] auto MyMethod() const;` +- **Static methods**: `[[deprecated("...")]] static auto MyStaticMethod();` +- **Constructors**: `[[deprecated("...")]] MyClass(args);` +- **Classes, interfaces, delegates, structs**: A redeclaration with `[[deprecated]]` is emitted after the full definition + +## Compiler Behavior + +When you use a deprecated API, the compiler emits warning **C4996** (MSVC) or equivalent: + +``` +warning C4996: 'winrt::Windows::Media::PlayTo::PlayToConnection': PlayToConnection may be altered or unavailable for releases after Windows 10. Instead, use CastingConnection. +``` + +The code still **compiles and works** — deprecated warnings are informational only. + +## Suppressing Warnings + +If you intentionally use a deprecated API and want to suppress the warning: + +```cpp +#pragma warning(push) +#pragma warning(disable: 4996) + +// Use deprecated APIs here +auto connection = winrt::Windows::Media::PlayTo::PlayToConnection{}; + +#pragma warning(pop) +``` + +Or for a single line: + +```cpp +#pragma warning(suppress: 4996) +auto connection = winrt::Windows::Media::PlayTo::PlayToConnection{}; +``` + +## Implementation Details + +For types (classes, interfaces, delegates, structs), the deprecated annotation uses a **redeclaration pattern** to avoid cascading warnings within the projection's own internal code: + +```cpp +// Full definition without [[deprecated]] — used internally without warnings +struct WINRT_IMPL_EMPTY_BASES PlayToConnection : + winrt::Windows::Media::PlayTo::IPlayToConnection +{ + PlayToConnection(std::nullptr_t) noexcept {} + // ... +}; +// Redeclaration adds [[deprecated]] — triggers warnings in consumer code +struct [[deprecated("PlayToConnection may be altered or unavailable...")]] PlayToConnection; +``` + +For methods and enum values, `[[deprecated]]` is applied directly on the declaration. diff --git a/test/old_tests/UnitTests/delegate.cpp b/test/old_tests/UnitTests/delegate.cpp index 5951b7bf6..59abae988 100644 --- a/test/old_tests/UnitTests/delegate.cpp +++ b/test/old_tests/UnitTests/delegate.cpp @@ -1,6 +1,8 @@ #include "pch.h" #include "catch.hpp" +#pragma warning(disable: 4996) // deprecated APIs used intentionally in tests + using namespace winrt; using namespace Windows; using namespace Windows::Graphics::Display; diff --git a/test/test/deprecated.cpp b/test/test/deprecated.cpp new file mode 100644 index 000000000..5ebce269d --- /dev/null +++ b/test/test/deprecated.cpp @@ -0,0 +1,33 @@ +#include "pch.h" + +// Suppress C4996 (deprecated) warnings so the test compiles cleanly +#pragma warning(push) +#pragma warning(disable: 4996) + +#include "winrt/test_component.h" + +using namespace winrt; + +TEST_CASE("deprecated_enum_compiles") +{ + auto val = test_component::DeprecatedEnum::First; + REQUIRE(val == test_component::DeprecatedEnum::First); + REQUIRE(static_cast(val) == 0); +} + +TEST_CASE("deprecated_class_compiles") +{ + test_component::DeprecatedClass obj{ nullptr }; + REQUIRE(!obj); +} + +TEST_CASE("deprecated_method_on_class_compiles") +{ + // Verify that deprecated static methods can be referenced without error. + // We cannot actually call them without an implementation, but we can verify + // the generated code compiles. + auto fn_ptr = &test_component::DeprecatedClass::OldStaticMethod; + REQUIRE(fn_ptr != nullptr); +} + +#pragma warning(pop) diff --git a/test/test/removed.cpp b/test/test/removed.cpp new file mode 100644 index 000000000..b14d866cc --- /dev/null +++ b/test/test/removed.cpp @@ -0,0 +1,108 @@ +#include "pch.h" + +// Suppress C4996 (deprecated) warnings so the test compiles cleanly +#pragma warning(push) +#pragma warning(disable: 4996) + +#include "winrt/test_component.h" + +using namespace winrt; + +// ============================================================================ +// Test 1: Removed enum is completely absent from user namespace +// ============================================================================ +TEST_CASE("removed_enum_is_absent") +{ + // RemovedEnum should not be accessible. If it were, this test would + // fail at compile time. We verify at runtime that the projection only + // contains the non-removed enum. + // static_assert(!std::is_class_v); // Would not compile if type existed + + // PartiallyRemovedEnum should exist + auto val = test_component::PartiallyRemovedEnum::Visible; + REQUIRE(val == test_component::PartiallyRemovedEnum::Visible); + REQUIRE(static_cast(val) == 0); + + auto val2 = test_component::PartiallyRemovedEnum::AlsoVisible; + REQUIRE(static_cast(val2) == 2); +} + +// ============================================================================ +// Test 2: PartiallyRemovedEnum - Hidden value should not be present +// ============================================================================ +TEST_CASE("partially_removed_enum_hidden_value_absent") +{ + // The Hidden value (=1) was removed. We verify that Visible (0) and + // AlsoVisible (2) are present but there is no way to reference Hidden + // through the projected enum. We can verify by value: + auto visible = test_component::PartiallyRemovedEnum::Visible; + auto also_visible = test_component::PartiallyRemovedEnum::AlsoVisible; + REQUIRE(static_cast(visible) == 0); + REQUIRE(static_cast(also_visible) == 2); + // If Hidden were still there, test_component::PartiallyRemovedEnum::Hidden would compile +} + +// ============================================================================ +// Test 3: RemovedClass - active methods work, removed are absent +// ============================================================================ +TEST_CASE("removed_class_active_methods") +{ + // RemovedClass itself should still exist (not all methods removed), + // but ActiveMethod and ActiveStaticMethod should be the only accessible ones. + test_component::RemovedClass obj{ nullptr }; + REQUIRE(!obj); + + // ActiveStaticMethod should be callable (it's a function pointer we can reference) + auto fn_ptr = &test_component::RemovedClass::ActiveStaticMethod; + REQUIRE(fn_ptr != nullptr); + + // RemovedStaticMethod should NOT compile if referenced: + // auto bad_ptr = &test_component::RemovedClass::RemovedStaticMethod; // Would fail +} + +// ============================================================================ +// Test 4: VTable slot preservation - ABI struct has ALL methods including removed +// ============================================================================ +TEST_CASE("vtable_slots_preserved_for_removed_methods") +{ + // The ABI vtable struct for IVtableTest must include ALL method slots + // (NormalMethod, DeprecatedMethod, RemovedMethod, AnotherNormalMethod) + // even though RemovedMethod is hidden from the user projection. + // We verify by instantiating a pointer to the ABI vtable and confirming + // we can take the address of all vtable method pointers, including removed ones. + + using abi_vtable = winrt::impl::abi_t; + + // Verify the vtable struct has all 4 method pointers by taking member pointers. + // If RemovedMethod were stripped from the vtable, this would not compile. + auto p1 = &abi_vtable::NormalMethod; + auto p2 = &abi_vtable::DeprecatedMethod; + auto p3 = &abi_vtable::RemovedMethod; + auto p4 = &abi_vtable::AnotherNormalMethod; + + REQUIRE(p1 != nullptr); + REQUIRE(p2 != nullptr); + REQUIRE(p3 != nullptr); + REQUIRE(p4 != nullptr); + + // All 4 method pointers must be distinct + REQUIRE(p1 != p3); + REQUIRE(p2 != p3); + REQUIRE(p4 != p3); +} + +// ============================================================================ +// Test 5: Deprecated methods still accessible (not removed) +// ============================================================================ +TEST_CASE("deprecated_still_accessible_not_removed") +{ + // DeprecatedEnum should still compile (with C4996 suppressed) + auto dep_val = test_component::DeprecatedEnum::First; + REQUIRE(static_cast(dep_val) == 0); + + // DeprecatedClass methods should still be accessible + auto dep_fn = &test_component::DeprecatedClass::OldStaticMethod; + REQUIRE(dep_fn != nullptr); +} + +#pragma warning(pop) diff --git a/test/test/test.vcxproj b/test/test/test.vcxproj index 7840f17eb..c39cee23c 100644 --- a/test/test/test.vcxproj +++ b/test/test/test.vcxproj @@ -224,6 +224,8 @@ + + diff --git a/test/test_component/DeprecatedClass.cpp b/test/test_component/DeprecatedClass.cpp new file mode 100644 index 000000000..85f20cae9 --- /dev/null +++ b/test/test_component/DeprecatedClass.cpp @@ -0,0 +1,22 @@ +#include "pch.h" +#include "DeprecatedClass.h" +#include "DeprecatedClass.g.cpp" + +namespace winrt::test_component::implementation +{ + void DeprecatedClass::StaticMethod() + { + } + + void DeprecatedClass::OldStaticMethod() + { + } + + void DeprecatedClass::Method() + { + } + + void DeprecatedClass::OldMethod() + { + } +} diff --git a/test/test_component/DeprecatedClass.h b/test/test_component/DeprecatedClass.h new file mode 100644 index 000000000..06c3c3efa --- /dev/null +++ b/test/test_component/DeprecatedClass.h @@ -0,0 +1,21 @@ +#pragma once +#include "DeprecatedClass.g.h" + +namespace winrt::test_component::implementation +{ + struct DeprecatedClass : DeprecatedClassT + { + DeprecatedClass() = default; + + static void StaticMethod(); + static void OldStaticMethod(); + void Method(); + void OldMethod(); + }; +} +namespace winrt::test_component::factory_implementation +{ + struct DeprecatedClass : DeprecatedClassT + { + }; +} diff --git a/test/test_component/RemovedClass.cpp b/test/test_component/RemovedClass.cpp new file mode 100644 index 000000000..54e73a070 --- /dev/null +++ b/test/test_component/RemovedClass.cpp @@ -0,0 +1,24 @@ +#include "pch.h" +#include "RemovedClass.h" +#include "RemovedClass.g.cpp" + +namespace winrt::test_component::implementation +{ + void RemovedClass::ActiveStaticMethod() + { + } + + void RemovedClass::RemovedStaticMethod() + { + throw hresult_not_implemented(); + } + + void RemovedClass::ActiveMethod() + { + } + + void RemovedClass::RemovedMethod() + { + throw hresult_not_implemented(); + } +} diff --git a/test/test_component/RemovedClass.h b/test/test_component/RemovedClass.h new file mode 100644 index 000000000..11fa170dc --- /dev/null +++ b/test/test_component/RemovedClass.h @@ -0,0 +1,21 @@ +#pragma once +#include "RemovedClass.g.h" + +namespace winrt::test_component::implementation +{ + struct RemovedClass : RemovedClassT + { + RemovedClass() = default; + + static void ActiveStaticMethod(); + static void RemovedStaticMethod(); + void ActiveMethod(); + void RemovedMethod(); + }; +} +namespace winrt::test_component::factory_implementation +{ + struct RemovedClass : RemovedClassT + { + }; +} diff --git a/test/test_component/test_component.idl b/test/test_component/test_component.idl index 536a703f5..de369ea9b 100644 --- a/test/test_component/test_component.idl +++ b/test/test_component/test_component.idl @@ -381,4 +381,121 @@ namespace test_component [overridable] interface test_component.IOverloadClassOverrides; [overridable] interface test_component.IOverloadClassOverrides2; } + + [deprecated("DeprecatedEnum is deprecated for testing.", deprecate, 1)] + enum DeprecatedEnum + { + First = 0, + Second = 1, + Third = 2 + }; + + delegate void DeprecatedDelegate(); + + runtimeclass DeprecatedClass + { + DeprecatedClass(); + void Method(); + [deprecated("OldMethod is deprecated for testing.", deprecate, 1)] + void OldMethod(); + static void StaticMethod(); + [deprecated("OldStaticMethod is deprecated for testing.", deprecate, 1)] + static void OldStaticMethod(); + + // Properties: normal and deprecated + String NormalProp{ get; }; + [deprecated("DeprecatedProp is deprecated.", deprecate, 1)] + String DeprecatedProp{ get; }; + + // Read-write properties: normal and deprecated + String WritableProp; + [deprecated("WritableDeprecatedProp is deprecated.", deprecate, 1)] + String WritableDeprecatedProp; + + // Events: normal and deprecated + event Windows.Foundation.EventHandler NormalEvent; + [deprecated("DeprecatedEvent is deprecated.", deprecate, 1)] + event Windows.Foundation.EventHandler DeprecatedEvent; + + // Static properties: normal and deprecated + static String StaticProp{ get; }; + [deprecated("StaticDeprecatedProp is deprecated.", deprecate, 1)] + static String StaticDeprecatedProp{ get; }; + + // Constructor overload: deprecated + [deprecated("Use default constructor instead.", deprecate, 1)] + DeprecatedClass(String name); + } + + [deprecated("RemovedEnum has been removed.", remove, 2)] + enum RemovedEnum + { + Alpha = 0, + Beta = 1 + }; + + enum PartiallyRemovedEnum + { + Visible = 0, + [deprecated("Hidden value has been removed.", remove, 2)] + Hidden = 1, + AlsoVisible = 2 + }; + + delegate void RemovedDelegate(); + + runtimeclass RemovedClass + { + RemovedClass(); + void ActiveMethod(); + [deprecated("RemovedMethod has been removed.", remove, 2)] + void RemovedMethod(); + static void ActiveStaticMethod(); + [deprecated("RemovedStaticMethod has been removed.", remove, 2)] + static void RemovedStaticMethod(); + + // Properties: normal and removed + String NormalProp{ get; }; + [deprecated("RemovedProp has been removed.", remove, 2)] + String RemovedProp{ get; }; + + // Read-write properties: normal and removed + String WritableProp; + [deprecated("WritableRemovedProp has been removed.", remove, 2)] + String WritableRemovedProp; + + // Events: normal and removed + event Windows.Foundation.EventHandler NormalEvent; + [deprecated("RemovedEvent has been removed.", remove, 2)] + event Windows.Foundation.EventHandler RemovedEvent; + + // Static properties: normal and removed + static String StaticProp{ get; }; + [deprecated("StaticRemovedProp has been removed.", remove, 2)] + static String StaticRemovedProp{ get; }; + + // Constructor overload: removed + [deprecated("Removed constructor.", remove, 2)] + RemovedClass(String name); + } + + // Entirely removed class — component stubs and module.g.cpp should NOT reference this + [deprecated("FullyRemovedClass has been removed.", remove, 2)] + [default_interface] + runtimeclass FullyRemovedClass + { + FullyRemovedClass(); + void DoSomething(); + } + + // Interface with mix of normal, deprecated, and removed methods for vtable testing + interface IVtableTest + { + void NormalMethod(); + [deprecated("DeprecatedMethod is deprecated.", deprecate, 1)] + void DeprecatedMethod(); + [deprecated("RemovedMethod is gone.", remove, 2)] + void RemovedMethod(); + void AnotherNormalMethod(); + } } diff --git a/test/test_component/test_component.vcxproj b/test/test_component/test_component.vcxproj index 3ffdb8f97..08537da44 100644 --- a/test/test_component/test_component.vcxproj +++ b/test/test_component/test_component.vcxproj @@ -387,6 +387,8 @@ + + @@ -404,6 +406,8 @@ + + diff --git a/test/verify_deprecated.ps1 b/test/verify_deprecated.ps1 new file mode 100644 index 000000000..dace03fc6 --- /dev/null +++ b/test/verify_deprecated.ps1 @@ -0,0 +1,85 @@ +<# +.SYNOPSIS + Verifies that generated C++/WinRT headers contain [[deprecated]] annotations + for types known to be deprecated in Windows SDK metadata. + +.DESCRIPTION + Runs cppwinrt.exe to generate headers for a namespace with deprecated types, + then checks that the output contains expected [[deprecated("...")]] annotations. +#> +param( + [string]$CppWinRTExe = "$PSScriptRoot\..\_build\x64\Release\cppwinrt.exe", + [string]$OutDir = "$env:TEMP\cppwinrt_deprecated_verify" +) + +$ErrorActionPreference = "Stop" + +if (-not (Test-Path $CppWinRTExe)) { + Write-Error "cppwinrt.exe not found at $CppWinRTExe. Build the project first." + exit 1 +} + +# Clean output directory +if (Test-Path $OutDir) { Remove-Item $OutDir -Recurse -Force } +New-Item $OutDir -ItemType Directory | Out-Null + +Write-Host "Generating projection for Windows.Media.PlayTo..." -ForegroundColor Cyan +& $CppWinRTExe -in local -out $OutDir -include "Windows.Media.PlayTo" 2>&1 | Out-Null + +$failures = 0 +$passes = 0 + +function Assert-FileContains($File, $Pattern, $Description) { + $match = Select-String -Path $File -Pattern $Pattern -Quiet + if ($match) { + Write-Host " PASS: $Description" -ForegroundColor Green + $script:passes++ + } else { + Write-Host " FAIL: $Description" -ForegroundColor Red + $script:failures++ + } +} + +function Assert-FileNotContains($File, $Pattern, $Description) { + $match = Select-String -Path $File -Pattern $Pattern -Quiet + if (-not $match) { + Write-Host " PASS: $Description" -ForegroundColor Green + $script:passes++ + } else { + Write-Host " FAIL: $Description (found unexpected match)" -ForegroundColor Red + $script:failures++ + } +} + +Write-Host "`nChecking deprecated enum annotations..." -ForegroundColor Cyan +$header0 = "$OutDir\winrt\impl\Windows.Media.PlayTo.0.h" +Assert-FileContains $header0 '\[\[deprecated\("PlayToConnectionError' "Deprecated enum PlayToConnectionError" +Assert-FileContains $header0 '\[\[deprecated\("PlayToConnectionState' "Deprecated enum PlayToConnectionState" + +Write-Host "`nChecking deprecated method annotations..." -ForegroundColor Cyan +Assert-FileContains $header0 '\[\[deprecated\(.*\)\]\].*auto State\(\) const' "Deprecated method State() on IPlayToConnection" + +Write-Host "`nChecking deprecated interface redeclarations..." -ForegroundColor Cyan +$headerMain = "$OutDir\winrt\Windows.Media.PlayTo.h" +Assert-FileContains $headerMain 'struct \[\[deprecated\(.*\)\]\] IPlayToConnection;' "Deprecated interface redeclaration IPlayToConnection" +Assert-FileContains $headerMain 'struct \[\[deprecated\(.*\)\]\] IPlayToManager;' "Deprecated interface redeclaration IPlayToManager" + +Write-Host "`nChecking deprecated class redeclarations..." -ForegroundColor Cyan +Assert-FileContains $headerMain 'struct \[\[deprecated\(.*\)\]\] PlayToConnection;' "Deprecated class redeclaration PlayToConnection" +Assert-FileContains $headerMain 'struct \[\[deprecated\(.*\)\]\] PlayToManager;' "Deprecated class redeclaration PlayToManager" +Assert-FileContains $headerMain 'struct \[\[deprecated\(.*\)\]\] PlayToSource;' "Deprecated class redeclaration PlayToSource" + +Write-Host "`nChecking deprecated static methods..." -ForegroundColor Cyan +$header2 = "$OutDir\winrt\impl\Windows.Media.PlayTo.2.h" +Assert-FileContains $header2 '\[\[deprecated\(.*\)\]\].*static auto GetForCurrentView' "Deprecated static method GetForCurrentView" + +Write-Host "`nChecking non-deprecated types are clean..." -ForegroundColor Cyan +Assert-FileNotContains $headerMain 'struct \[\[deprecated.*PlayToReceiver;' "PlayToReceiver should NOT be deprecated" + +Write-Host "`n---" +Write-Host "Results: $passes passed, $failures failed" -ForegroundColor $(if ($failures -eq 0) { "Green" } else { "Red" }) + +# Cleanup +Remove-Item $OutDir -Recurse -Force + +if ($failures -gt 0) { exit 1 } diff --git a/test/verify_deprecated_pch.ps1 b/test/verify_deprecated_pch.ps1 new file mode 100644 index 000000000..f08c96c4e --- /dev/null +++ b/test/verify_deprecated_pch.ps1 @@ -0,0 +1,128 @@ +<# +.SYNOPSIS + Verifies that [[deprecated]] annotations work correctly with precompiled headers (PCH). + +.DESCRIPTION + Generates projection headers for a deprecated Windows SDK namespace, creates a PCH + that includes those headers, then verifies: + 1. PCH creation succeeds with /W4 /WX + 2. Source files using the PCH but NOT using deprecated APIs compile clean + 3. Source files using deprecated APIs WITH pragma suppress compile clean + 4. Source files using deprecated APIs WITHOUT suppress trigger C4996 warning +#> +[CmdLetBinding()] +Param( + [string] $CppWinRTExe = "G:\cppwinrt\_build\x64\Release\cppwinrt.exe" +) + +$ErrorActionPreference = "Continue" +$passed = 0 +$failed = 0 + +function Test-Check { + param([string]$Name, [bool]$Condition) + if ($Condition) { + Write-Host " PASS: $Name" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: $Name" -ForegroundColor Red + $script:failed++ + } +} + +# Find MSVC +$clPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC" +$clVersion = Get-ChildItem $clPath | Sort-Object Name -Descending | Select-Object -First 1 +$cl = "$($clVersion.FullName)\bin\Hostx64\x64\cl.exe" + +# Find Windows SDK +$sdkInc = Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\Include" | Sort-Object Name -Descending | Select-Object -First 1 +$sdkIncPath = $sdkInc.FullName + +Write-Host "MSVC: $cl" +Write-Host "SDK: $sdkIncPath" + +# Create temp directories +$genDir = Join-Path $env:TEMP "pch_deprecated_gen_$(Get-Random)" +$testDir = Join-Path $env:TEMP "pch_deprecated_test_$(Get-Random)" +New-Item -ItemType Directory -Path $genDir, $testDir -Force | Out-Null + +try { + # Generate projection for a deprecated namespace + Write-Host "`nGenerating projection for Windows.Media.PlayTo..." + & $CppWinRTExe -in sdk -out $genDir -filter Windows.Media.PlayTo + if ($LASTEXITCODE -ne 0) { throw "cppwinrt.exe failed" } + + $commonArgs = @( + "/std:c++20", "/W4", "/WX", "/EHsc", "/MD", + "/I", $genDir, + "/I", "$sdkIncPath\ucrt", + "/I", "$sdkIncPath\um", + "/I", "$sdkIncPath\shared", + "/I", "$sdkIncPath\winrt", + "/I", "$sdkIncPath\cppwinrt", + "/I", "$($clVersion.FullName)\include" + ) + + # Create PCH header + @" +#pragma once +#include "winrt/Windows.Foundation.h" +#include "winrt/Windows.Media.PlayTo.h" +"@ | Set-Content "$testDir\pch.h" + @" +#include "pch.h" +"@ | Set-Content "$testDir\pch.cpp" + + # Step 1: Create PCH + Write-Host "`n=== PCH Creation ===" + & $cl @commonArgs /Yc"pch.h" /Fp"$testDir\pch.pch" /c "$testDir\pch.cpp" /Fo"$testDir\pch.obj" 2>&1 | Out-Null + Test-Check "PCH creation with deprecated headers succeeds with /W4 /WX" ($LASTEXITCODE -eq 0) + + # Step 2: Clean compilation (no deprecated usage) + @" +#include "pch.h" +void clean_test() { + winrt::Windows::Foundation::Uri uri{ L"http://example.com" }; + (void)uri; +} +"@ | Set-Content "$testDir\test_clean.cpp" + & $cl @commonArgs /Yu"pch.h" /Fp"$testDir\pch.pch" /c "$testDir\test_clean.cpp" /Fo"$testDir\test_clean.obj" 2>&1 | Out-Null + Test-Check "Source using PCH without deprecated usage compiles clean" ($LASTEXITCODE -eq 0) + + # Step 3: Deprecated usage with suppression + @" +#include "pch.h" +#pragma warning(push) +#pragma warning(disable: 4996) +void suppressed_test() { + auto s = winrt::Windows::Media::PlayTo::PlayToConnectionState::Connected; + (void)s; +} +#pragma warning(pop) +"@ | Set-Content "$testDir\test_suppress.cpp" + & $cl @commonArgs /Yu"pch.h" /Fp"$testDir\pch.pch" /c "$testDir\test_suppress.cpp" /Fo"$testDir\test_suppress.obj" 2>&1 | Out-Null + Test-Check "Deprecated usage with pragma suppress compiles clean with /WX" ($LASTEXITCODE -eq 0) + + # Step 4: Deprecated usage without suppression (should warn, not error) + @" +#include "pch.h" +void warn_test() { + auto s = winrt::Windows::Media::PlayTo::PlayToConnectionState::Connected; + (void)s; +} +"@ | Set-Content "$testDir\test_warn.cpp" + $noWxArgs = $commonArgs | ForEach-Object { if ($_ -ne "/WX") { $_ } } + $warnOutput = & $cl @noWxArgs /Yu"pch.h" /Fp"$testDir\pch.pch" /c "$testDir\test_warn.cpp" /Fo"$testDir\test_warn.obj" 2>&1 | Out-String + Test-Check "Deprecated usage without suppress triggers C4996 warning" ($warnOutput -match "C4996") + Test-Check "Warning message includes deprecation text" ($warnOutput -match "PlayToConnectionState") + + Write-Host "`n=== Results ===" + Write-Host "$passed passed, $failed failed out of $($passed + $failed) checks" + + if ($failed -gt 0) { throw "$failed check(s) failed" } + Write-Host "`nAll PCH checks passed!" -ForegroundColor Green +} +finally { + Remove-Item -Recurse -Force $genDir, $testDir -ErrorAction SilentlyContinue +} diff --git a/test/verify_removed.ps1 b/test/verify_removed.ps1 new file mode 100644 index 000000000..ac3dcf874 --- /dev/null +++ b/test/verify_removed.ps1 @@ -0,0 +1,332 @@ +# Verify removed API support in CPPWinRT generated headers +# This script checks the generated .0.h and .h files to verify: +# 1. ABI vtable slots are preserved for removed methods +# 2. User-facing consume structs exclude removed methods +# 3. Deprecated methods remain visible with [[deprecated]] +# 4. Normal methods are present in both ABI and consume +# 5. Component generation (-component) skips fully removed classes + +param( + [string]$GeneratedDir = "test\test_component\Generated Files\winrt", + [string]$ComponentDir = "test\test_component\Generated Files" +) + +$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 [regex]::Escape($Pattern)) { + Write-Host " PASS: $Message" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: $Message" -ForegroundColor Red + Write-Host " Expected to find: $Pattern" -ForegroundColor Yellow + $script:failed++ + } +} + +function Assert-NotContains { + param([string]$File, [string]$Pattern, [string]$Message) + $content = Get-Content $File -Raw + if ($content -match [regex]::Escape($Pattern)) { + Write-Host " FAIL: $Message" -ForegroundColor Red + Write-Host " Expected NOT to find: $Pattern" -ForegroundColor Yellow + $script:failed++ + } else { + Write-Host " PASS: $Message" -ForegroundColor Green + $script:passed++ + } +} + +$h0 = Join-Path $GeneratedDir "impl\test_component.0.h" + +if (!(Test-Path $h0)) { + Write-Host "ERROR: Generated files not found at $h0" -ForegroundColor Red + Write-Host "Run the cppwinrt build first." -ForegroundColor Yellow + exit 1 +} + +Write-Host "`n=== IVtableTest: ABI vtable slot preservation ===" -ForegroundColor Cyan + +# ABI vtable must have ALL 4 methods (normal + deprecated + removed + normal) +Assert-Contains $h0 "virtual int32_t __stdcall NormalMethod() noexcept = 0;" ` + "ABI vtable has NormalMethod slot" +Assert-Contains $h0 "virtual int32_t __stdcall DeprecatedMethod() noexcept = 0;" ` + "ABI vtable has DeprecatedMethod slot" +Assert-Contains $h0 "virtual int32_t __stdcall RemovedMethod() noexcept = 0;" ` + "ABI vtable has RemovedMethod slot (preserved for binary compat)" +Assert-Contains $h0 "virtual int32_t __stdcall AnotherNormalMethod() noexcept = 0;" ` + "ABI vtable has AnotherNormalMethod slot" + +Write-Host "`n=== IVtableTest: User consume struct ===" -ForegroundColor Cyan + +# Consume struct should have normal + deprecated, but NOT removed +Assert-Contains $h0 "auto NormalMethod() const;" ` + "Consume struct has NormalMethod" +Assert-Contains $h0 "auto DeprecatedMethod() const;" ` + "Consume struct has DeprecatedMethod" +Assert-NotContains $h0 "auto RemovedMethod() const;" ` + "Consume struct does NOT have RemovedMethod (hidden)" +Assert-Contains $h0 "auto AnotherNormalMethod() const;" ` + "Consume struct has AnotherNormalMethod" + +Write-Host "`n=== IVtableTest: Deprecated annotation ===" -ForegroundColor Cyan + +Assert-Contains $h0 '[[deprecated("DeprecatedMethod is deprecated.")]] auto DeprecatedMethod() const;' ` + "DeprecatedMethod has [[deprecated]] annotation" + +Write-Host "`n=== IRemovedClass: method-level removal ===" -ForegroundColor Cyan + +# ABI should have both ActiveMethod and RemovedMethod +Assert-Contains $h0 "virtual int32_t __stdcall ActiveMethod() noexcept = 0;" ` + "IRemovedClass ABI vtable has ActiveMethod" +Assert-Contains $h0 "virtual int32_t __stdcall RemovedMethod() noexcept = 0;" ` + "IRemovedClass ABI vtable has RemovedMethod (preserved)" + +# Consume should only have ActiveMethod +$consumeSection = (Get-Content $h0 -Raw) -match 'struct consume_test_component_IRemovedClass\s*\{([^}]+)\}' +if ($consumeSection) { + $body = $Matches[1] + if ($body -match "ActiveMethod") { + Write-Host " PASS: IRemovedClass consume has ActiveMethod" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClass consume missing ActiveMethod" -ForegroundColor Red + $script:failed++ + } + if ($body -notmatch "RemovedMethod") { + Write-Host " PASS: IRemovedClass consume does NOT have RemovedMethod" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClass consume still has RemovedMethod" -ForegroundColor Red + $script:failed++ + } +} + +Write-Host "`n=== IRemovedClass: property-level removal ===" -ForegroundColor Cyan + +# ABI vtable must preserve ALL property accessors (normal + removed) +Assert-Contains $h0 "virtual int32_t __stdcall get_NormalProp(void**) noexcept = 0;" ` + "IRemovedClass ABI vtable has get_NormalProp (preserved)" +Assert-Contains $h0 "virtual int32_t __stdcall get_RemovedProp(void**) noexcept = 0;" ` + "IRemovedClass ABI vtable has get_RemovedProp (preserved)" + +# Consume struct: normal prop present, removed prop hidden +$consumeSection = (Get-Content $h0 -Raw) -match 'struct consume_test_component_IRemovedClass\s*\{([^}]+)\}' +if ($consumeSection) { + $body = $Matches[1] + if ($body -match "NormalProp") { + Write-Host " PASS: IRemovedClass consume has NormalProp" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClass consume missing NormalProp" -ForegroundColor Red + $script:failed++ + } + if ($body -notmatch "RemovedProp") { + Write-Host " PASS: IRemovedClass consume does NOT have RemovedProp" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClass consume still has RemovedProp" -ForegroundColor Red + $script:failed++ + } +} + +Write-Host "`n=== IRemovedClass: read-write property removal ===" -ForegroundColor Cyan + +# ABI: both writable props preserved +Assert-Contains $h0 "virtual int32_t __stdcall get_WritableRemovedProp(void**) noexcept = 0;" ` + "IRemovedClass ABI vtable has get_WritableRemovedProp (preserved)" +Assert-Contains $h0 "virtual int32_t __stdcall put_WritableRemovedProp(void*) noexcept = 0;" ` + "IRemovedClass ABI vtable has put_WritableRemovedProp (preserved)" + +# Consume struct: normal writable prop present, removed writable prop hidden +$consumeSection = (Get-Content $h0 -Raw) -match 'struct consume_test_component_IRemovedClass\s*\{([^}]+)\}' +if ($consumeSection) { + $body = $Matches[1] + if ($body -match "WritableProp") { + Write-Host " PASS: IRemovedClass consume has WritableProp" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClass consume missing WritableProp" -ForegroundColor Red + $script:failed++ + } + if ($body -notmatch "WritableRemovedProp") { + Write-Host " PASS: IRemovedClass consume does NOT have WritableRemovedProp" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClass consume still has WritableRemovedProp" -ForegroundColor Red + $script:failed++ + } +} + +Write-Host "`n=== IRemovedClass: event-level removal ===" -ForegroundColor Cyan + +# ABI: both events preserved +Assert-Contains $h0 "virtual int32_t __stdcall add_RemovedEvent(void*, winrt::event_token*) noexcept = 0;" ` + "IRemovedClass ABI vtable has add_RemovedEvent (preserved)" +Assert-Contains $h0 "virtual int32_t __stdcall remove_RemovedEvent(winrt::event_token) noexcept = 0;" ` + "IRemovedClass ABI vtable has remove_RemovedEvent (preserved)" + +# Consume struct: normal event present, removed event hidden +$consumeSection = (Get-Content $h0 -Raw) -match 'struct consume_test_component_IRemovedClass\s*\{([^}]+)\}' +if ($consumeSection) { + $body = $Matches[1] + if ($body -match "NormalEvent") { + Write-Host " PASS: IRemovedClass consume has NormalEvent" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClass consume missing NormalEvent" -ForegroundColor Red + $script:failed++ + } + if ($body -notmatch "RemovedEvent") { + Write-Host " PASS: IRemovedClass consume does NOT have RemovedEvent" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClass consume still has RemovedEvent" -ForegroundColor Red + $script:failed++ + } +} + +Write-Host "`n=== IRemovedClassStatics: static property removal ===" -ForegroundColor Cyan + +# ABI: both static props preserved +Assert-Contains $h0 "virtual int32_t __stdcall get_StaticRemovedProp(void**) noexcept = 0;" ` + "IRemovedClassStatics ABI vtable has get_StaticRemovedProp (preserved)" + +# Consume struct +$consumeSection = (Get-Content $h0 -Raw) -match 'struct consume_test_component_IRemovedClassStatics\s*\{([^}]+)\}' +if ($consumeSection) { + $body = $Matches[1] + if ($body -match "StaticProp") { + Write-Host " PASS: IRemovedClassStatics consume has StaticProp" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClassStatics consume missing StaticProp" -ForegroundColor Red + $script:failed++ + } + if ($body -notmatch "StaticRemovedProp") { + Write-Host " PASS: IRemovedClassStatics consume does NOT have StaticRemovedProp" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClassStatics consume still has StaticRemovedProp" -ForegroundColor Red + $script:failed++ + } +} + +Write-Host "`n=== IRemovedClassFactory: constructor removal ===" -ForegroundColor Cyan + +# ABI: removed constructor factory method preserved +Assert-Contains $h0 "virtual int32_t __stdcall CreateInstance(void*, void**) noexcept = 0;" ` + "IRemovedClassFactory ABI vtable has CreateInstance (preserved)" + +# Consume struct should be empty (removed constructor hidden) +$consumeSection = (Get-Content $h0 -Raw) -match 'struct consume_test_component_IRemovedClassFactory\s*\{([^}]+)\}' +if ($consumeSection) { + $body = $Matches[1].Trim() + if ($body -eq "") { + Write-Host " PASS: IRemovedClassFactory consume is empty (removed constructor hidden)" -ForegroundColor Green + $script:passed++ + } else { + Write-Host " FAIL: IRemovedClassFactory consume is not empty: $body" -ForegroundColor Red + $script:failed++ + } +} else { + Write-Host " PASS: IRemovedClassFactory consume section not found or empty" -ForegroundColor Green + $script:passed++ +} + +Write-Host "`n=== IDeprecatedClass: deprecated property annotations ===" -ForegroundColor Cyan + +Assert-Contains $h0 '[[deprecated("DeprecatedProp is deprecated.")]] [[nodiscard]] auto DeprecatedProp() const;' ` + "DeprecatedProp has [[deprecated]] annotation" +Assert-Contains $h0 '[[deprecated("WritableDeprecatedProp is deprecated.")]] [[nodiscard]] auto WritableDeprecatedProp() const;' ` + "WritableDeprecatedProp getter has [[deprecated]] annotation" +Assert-Contains $h0 '[[deprecated("WritableDeprecatedProp is deprecated.")]] auto WritableDeprecatedProp(param::hstring const& value) const;' ` + "WritableDeprecatedProp setter has [[deprecated]] annotation" + +Write-Host "`n=== IDeprecatedClass: deprecated event annotations ===" -ForegroundColor Cyan + +Assert-Contains $h0 '[[deprecated("DeprecatedEvent is deprecated.")]] auto DeprecatedEvent(winrt::Windows::Foundation::EventHandler const& handler) const;' ` + "DeprecatedEvent add has [[deprecated]] annotation" + +Write-Host "`n=== IDeprecatedClassStatics: deprecated static property annotations ===" -ForegroundColor Cyan + +Assert-Contains $h0 '[[deprecated("StaticDeprecatedProp is deprecated.")]] [[nodiscard]] auto StaticDeprecatedProp() const;' ` + "StaticDeprecatedProp has [[deprecated]] annotation" + +Write-Host "`n=== IDeprecatedClassFactory: deprecated constructor annotations ===" -ForegroundColor Cyan + +Assert-Contains $h0 '[[deprecated("Use default constructor instead.")]] auto CreateInstance(param::hstring const& name) const;' ` + "Deprecated constructor factory has [[deprecated]] annotation" + +Write-Host "`n=== PartiallyRemovedEnum: field-level removal ===" -ForegroundColor Cyan + +# Enum definitions are in the .0.h file +Assert-Contains $h0 "Visible = 0," "PartiallyRemovedEnum has Visible value" +Assert-Contains $h0 "AlsoVisible = 2," "PartiallyRemovedEnum has AlsoVisible value" +Assert-NotContains $h0 "Hidden = 1," "PartiallyRemovedEnum does NOT have Hidden value (removed)" + +Write-Host "`n=== RemovedEnum: fully removed enum ===" -ForegroundColor Cyan + +# RemovedEnum should NOT have its enum class definition (entire type removed) +Assert-NotContains $h0 "enum class RemovedEnum" "RemovedEnum type definition is excluded" + +Write-Host "`n=== FullyRemovedClass: component generation exclusion ===" -ForegroundColor Cyan + +# Fully removed class should not have a usable class definition in the main header +$mainH = Join-Path $GeneratedDir "test_component.h" +Assert-NotContains $mainH "struct FullyRemovedClass :" ` + "FullyRemovedClass has no user-visible class definition" + +# Component generation checks: module.g.cpp should NOT reference fully removed classes +$moduleGCpp = Join-Path $ComponentDir "module.g.cpp" +if (Test-Path $moduleGCpp) { + Assert-NotContains $moduleGCpp "FullyRemovedClass" ` + "module.g.cpp does NOT reference FullyRemovedClass" + + # Verify non-removed classes ARE still present + Assert-Contains $moduleGCpp "RemovedClass" ` + "module.g.cpp still references RemovedClass (has removed members, but class itself is not removed)" +} else { + Write-Host " SKIP: module.g.cpp not found (component mode not run)" -ForegroundColor Yellow +} + +# Component stubs should NOT be generated for fully removed classes +$removedStubH = Join-Path $ComponentDir "FullyRemovedClass.g.h" +$removedStubCpp = Join-Path $ComponentDir "FullyRemovedClass.g.cpp" +if (!(Test-Path $removedStubH)) { + Write-Host " PASS: FullyRemovedClass.g.h was NOT generated" -ForegroundColor Green + $script:passed++ +} else { + Write-Host " FAIL: FullyRemovedClass.g.h should NOT be generated for a fully removed class" -ForegroundColor Red + $script:failed++ +} +if (!(Test-Path $removedStubCpp)) { + Write-Host " PASS: FullyRemovedClass.g.cpp was NOT generated" -ForegroundColor Green + $script:passed++ +} else { + Write-Host " FAIL: FullyRemovedClass.g.cpp should NOT be generated for a fully removed class" -ForegroundColor Red + $script:failed++ +} + +# Non-removed classes with removed members should STILL get stubs +$removedClassStub = Join-Path $ComponentDir "RemovedClass.g.h" +if (Test-Path $removedClassStub) { + Write-Host " PASS: RemovedClass.g.h IS generated (class not removed, only some members)" -ForegroundColor Green + $script:passed++ +} else { + Write-Host " FAIL: RemovedClass.g.h should be generated (class itself is not removed)" -ForegroundColor Red + $script:failed++ +} + +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 +}