From e61a9a612b613bb8d6d28eb2299affcf508f289d Mon Sep 17 00:00:00 2001 From: "Chris Wall (WIN SDE)" Date: Mon, 2 Mar 2026 14:58:25 -0700 Subject: [PATCH 1/9] Add deprecated and removed API support Added is_deprecated(), get_deprecated_message(), and is_removed() helpers to helpers.h. When DeprecatedAttribute has DeprecationType.Remove (arg[1]==1): - Classes, delegates, enums, structs are completely skipped - Removed enum fields are skipped - Removed methods, properties, and events are skipped from interface member projections - ABI-level vtable structures are preserved for binary compatibility Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/cswinrt/code_writers.h | 40 +++++++++++++++++++++++++++++++-- src/cswinrt/helpers.h | 46 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 9971ae62c8..22a996ef47 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -5022,6 +5022,10 @@ remove => %.Unsubscribe(value); { continue; } + if (is_removed(method)) + { + continue; + } method_signature signature{ method }; auto [invoke_target, is_generic] = get_invoke_info(w, method); w.write(R"( @@ -5050,6 +5054,10 @@ remove => %.Unsubscribe(value); for (auto&& prop : type.PropertyList()) { + if (is_removed(prop)) + { + continue; + } auto [getter, setter] = get_property_methods(prop); w.write(R"( %unsafe % %% @@ -5110,7 +5118,11 @@ return %; int index = 0; for (auto&& evt : type.EventList()) { - auto semantics = get_type_semantics(evt.EventType()); + if (is_removed(evt)) + { + index++; + continue; + } auto event_source = w.write_temp(settings.netstandard_compat ? "_%" : "Get_%2()", evt.Name()); w.write(R"( %event % %% @@ -8642,6 +8654,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) @@ -9004,6 +9021,11 @@ public static ObjectReferenceValue CreateMarshaler2(% obj) => MarshalInterface<% void write_delegate(writer& w, TypeDef const& type) { + if (is_removed(type)) + { + return; + } + if (settings.component) { write_authoring_metadata_type(w, type); @@ -9687,6 +9709,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 +9740,10 @@ return true; { if (auto constant = field.Constant()) { + if (is_removed(field)) + { + continue; + } w.write("%% = unchecked((%)%),\n", bind(field.CustomAttribute()), field.Name(), enum_underlying_type, bind(constant)); @@ -9724,6 +9755,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); @@ -11153,4 +11189,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..1f421c5b19 100644 --- a/src/cswinrt/helpers.h +++ b/src/cswinrt/helpers.h @@ -34,6 +34,52 @@ 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; + } + bool is_exclusive_to(TypeDef const& type) { return get_category(type) == category::interface_type && has_attribute(type, "Windows.Foundation.Metadata"sv, "ExclusiveToAttribute"sv); From c0181f5f346c1caf3a30ae8ec7b08c148d715a9b Mon Sep 17 00:00:00 2001 From: "Chris Wall (WIN SDE)" Date: Mon, 2 Mar 2026 15:35:12 -0700 Subject: [PATCH 2/9] Add deprecated and removed API support - Fix pre-existing MSB4096 build error in Strings.props (qualify metadata) - Add is_deprecated(), get_deprecated_message(), is_removed(), is_deprecated_not_removed() helpers in helpers.h - Add write_obsolete_attribute() to emit [System.Obsolete] for deprecated types - Add [Obsolete] annotations for classes, delegates, enums, structs, methods, properties, events, and enum fields - Skip fully removed types/members from generated C# projection - cswinrt.exe builds and generates correct output Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Strings.props | 2 +- src/cswinrt/code_writers.h | 28 ++++++++++++ src/cswinrt/helpers.h | 6 +++ tests/verify_removed.ps1 | 91 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 tests/verify_removed.ps1 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 22a996ef47..8408b8067e 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -1816,6 +1816,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 +1899,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 @@ -5026,6 +5050,7 @@ remove => %.Unsubscribe(value); { continue; } + write_obsolete_attribute(w, method); method_signature signature{ method }; auto [invoke_target, is_generic] = get_invoke_info(w, method); w.write(R"( @@ -5058,6 +5083,7 @@ remove => %.Unsubscribe(value); { continue; } + write_obsolete_attribute(w, prop); auto [getter, setter] = get_property_methods(prop); w.write(R"( %unsafe % %% @@ -5123,6 +5149,7 @@ return %; index++; continue; } + write_obsolete_attribute(w, evt); auto event_source = w.write_temp(settings.netstandard_compat ? "_%" : "Get_%2()", evt.Name()); w.write(R"( %event % %% @@ -9744,6 +9771,7 @@ return true; { continue; } + write_obsolete_attribute(w, field); w.write("%% = unchecked((%)%),\n", bind(field.CustomAttribute()), field.Name(), enum_underlying_type, bind(constant)); diff --git a/src/cswinrt/helpers.h b/src/cswinrt/helpers.h index 1f421c5b19..d6bbe2b43c 100644 --- a/src/cswinrt/helpers.h +++ b/src/cswinrt/helpers.h @@ -80,6 +80,12 @@ namespace cswinrt 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/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 +} From 659fda2823265ebb56624c4783ff09a77da56e7d Mon Sep 17 00:00:00 2001 From: "Chris Wall (WIN SDE)" Date: Mon, 2 Mar 2026 16:04:45 -0700 Subject: [PATCH 3/9] Add end-to-end test with custom WinMD for deprecated/removed - Create test IDL with mix of normal, deprecated, and removed types: RemovedEnum, PartiallyRemovedEnum, DeprecatedEnum, NormalEnum, IVtableTest (normal/deprecated/removed methods), delegates, classes, structs - Compile to WinMD using MIDL 3.0 - run_test.ps1 verifies 22 checks: * Removed types excluded from user projection (enum, class, delegate, struct) * Deprecated types have [Obsolete] annotations with messages * Normal types present without [Obsolete] * PartiallyRemovedEnum: Hidden field excluded, Visible/AlsoVisible present * IVtableTest: ABI vtable preserves RemovedMethod slot * Method-level removal: removed methods hidden, deprecated have [Obsolete] Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DeprecatedRemovedTest.idl | 104 +++++++++++ .../DeprecatedRemovedTest.winmd | Bin 0 -> 4608 bytes tests/DeprecatedRemovedTest/run_test.ps1 | 175 ++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl create mode 100644 tests/DeprecatedRemovedTest/DeprecatedRemovedTest.winmd create mode 100644 tests/DeprecatedRemovedTest/run_test.ps1 diff --git a/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl new file mode 100644 index 0000000000..39c9a2f626 --- /dev/null +++ b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl @@ -0,0 +1,104 @@ +// Test IDL for verifying deprecated and removed API support +// Contains types with DeprecationType.Deprecate (0) and DeprecationType.Remove (1) +import "Windows.Foundation.idl"; + +namespace DeprecatedRemovedTest +{ + // 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 + }; + + // 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 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(); + + // Class with mix of active and removed methods + runtimeclass TestClass + { + TestClass(); + 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(); + } + + // Fully deprecated class + [deprecated("DeprecatedClass is deprecated.", deprecate, 1)] + runtimeclass DeprecatedClass + { + DeprecatedClass(); + void Method(); + } + + // Fully removed class - should NOT appear in projection + [deprecated("RemovedClass has been removed.", remove, 2)] + runtimeclass RemovedClass + { + RemovedClass(); + void Method(); + } + + // 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; + }; +} diff --git a/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.winmd b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.winmd new file mode 100644 index 0000000000000000000000000000000000000000..1e30ba3eb0a2fb49a43de27800f84e3e244a34b5 GIT binary patch literal 4608 zcmd^BYit}>6+U-nckQ)JuwBd8BxxpIyQOJa91;R31-y3L#C4rGv16-X8jp8x<7szy z%+9P6gP=xkt3Q)N0Jf$cIl41#|B2qy_N=ifEJ9lPx zW?jcZLPAK)YQDYaJ7>;o&YhWoPtuA<*NSW<$u@(%M0W{6N1LCvO4@zbh0c>B_?{(Xw{6U8;rfP_)@t@c~M2E5F_9k}j@7sa@Baqu8kh`k_*-m!(6y;|BJHsX9Y&b2rZ5_3hww~cLkaAL?*#_)l|@UxH6UDQhv+DkR~Jwj8! zd4tf5{pg(!~+zV<1_|6Jkk6rB%^=E~o^SWN)28G)d-U;NxyR&?6M2b{ZQC=&F zYl$L)CviwkXv?dc2E>Yvk^EIDL3F}&wj(fwp_fi~QxH~4KbxPtcnYiogNW@;I zej1s5jdlZn3oJ2j(ioV_3apQ}PK)tH=y)<&@^mH1)A5uBx{~DSuz}RE7;Vrlq5ma` zqX!aa7D$~HmOW)jr-zYQ$x^33iNGgIflrnKpE`Y=I<=Q|F9J(FWr@2~SS3QB`*$q` z{TQv&=mp@)>pJ=vreQCp>Tb--2~7Acz!oqk=ofSpctyMpyhJm^70wV>HbY#|8{li` zufQ|95GP2FGyoeTj}vElT46PI$UdBPGzx4|>lU?Mho?m~wJYX*is@AAtyp)^t-vn2 zfv(`$Q;P}h!P>x+hWT-^{crqn`uL4zctd~TONYFJPGifk`TH0STKMM~DZBFzl$aGYn(|!F$rjMdKWOE^#H%6_b7fG>nn5;D76=XXEo9p z4PZ(iS6D}F^e)`RcKEELo2UU((}}*;(>7X9ky)aD`agcj5auGpU@ho&rk7cLT5*8e z2kcBSD?d&<iQE$GOBmZKx z_Ft~aJM3aUW4e}|FTHO=RB}MNCbTAv$bti#l#AEwMA4O$OuKfWoB~wCGgGoG%S>Q7 zZDxz|zyV5SrzTD6hDRDS3$A5mvoqn0Ml8pg$V%E}Wim1kbH^@GLH#QCJFEiAAChT1 zpP}7$A!lY4zb7wg_>fI~Bd!Tgs!2PGRwx(G7*$Q$nNYGm;b-GZsl4q@%0k5@1I4Us zrA^1(A+z!zZb-;UnX)JDlWCW7UX;H4w0*xU^}cN6xh;!*oNQ0lbR1HR>^+>$79DF^ z4%uQ1F>69n$wnD2uz133)0NM z5<5s{sVoK-yvKBdk)ra4Z6B$=f9F&EQ%C+5x%eDa|LC)0BduF69E})4#H;bWqU@>+ zU94$nh;I_KJ~X(-r0EzFQs#|guZIq!HaR;|Yx;`rbsiZBhS^;SVQdP8ogy|J2u z2z%Kk=$7D~@TeP>V`Rz?1>;`3V7M3{6rAKx-WMBUt;e}>jZrJ&RaH^{Xc8oU`M~Gi zeq`~U&A&1npoEYPfg~gbIs1h!N-Qr?0SCt=?@UzcGTjh5O(v< zB$B6I{Zi(SFCF+v?^m99<->P(62af^`ZvAxkCyX$XSW~Q|FzaLgO~%{?Z#AlMHhVk zyl0ZZ#vyK1W%a8J0fL=9J@#O~yJhs$x%sX?Tsu`q^g%%#zK8JcTx}NM=Leg<{mhYv z9~%4eZ$7cjeX|Me#4vUQ?F^?Iu-?8Up1Wn_b=!_uGt+JN?SAe2*s(2FAG{!}lSDTQ zY6~2MWBKb)pfIyqQI$jIJtnT3RjBR0nul 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 user 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" + +# ============================================================================ +# 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" + +# ============================================================================ +# 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 + +# The ABI vtable must have all 4 methods including RemovedMethod +# Check for RemovedMethod in the ABI section (should exist for vtable) +Test-Result ($content -match 'RemovedMethod') ` + "RemovedMethod exists in ABI vtable code (binary compat)" + +# But user-facing interface should NOT have RemovedMethod +# The user interface IVtableTest should have NormalMethod but NOT RemovedMethod in its declaration +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 +# ============================================================================ +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]" + +# ============================================================================ +# 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 +} From 4c180d3f86f32243e8e3c8effac038af2580a71f Mon Sep 17 00:00:00 2001 From: "Chris Wall (WIN SDE)" Date: Wed, 4 Mar 2026 15:32:17 -0700 Subject: [PATCH 4/9] Fix deprecated/removed attribute support for class members write_class_method and write_class_event were missing is_removed() checks and write_obsolete_attribute() calls, so deprecated methods on runtime classes had no [Obsolete] attribute and removed methods were still visible. Also fixed write_interface_member_signatures to skip removed members and emit [Obsolete] on deprecated interface member signatures. Added is_removed() check and deprecation tracking for properties in write_class_members. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/cswinrt/code_writers.h | 55 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 8408b8067e..35b544f93d 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,11 @@ 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 = {}) { + if (is_removed(event)) + { + return; + } + auto visibility = "public "; if (is_protected) @@ -1552,6 +1566,7 @@ remove => %; bool is_private = is_implemented_as_private_method(w, class_type, add); if (!is_private) { + write_obsolete_attribute(w, event); 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); @@ -3302,6 +3317,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) @@ -3356,6 +3372,10 @@ private % AsInternal(InterfaceTag<%> _) => % ?? Make_%(); // Merge property getters/setters, since such may be defined across interfaces for (auto&& prop : interface_type.PropertyList()) { + if (is_removed(prop)) + { + continue; + } MethodDef getter, setter; std::tie(getter, setter) = get_property_methods(prop); auto prop_type = write_prop_type(w, prop); @@ -3373,6 +3393,10 @@ 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) + { + property_deprecation.try_emplace(property_name, prop); + } if (!inserted) { auto& [property_type, getter_target, getter_platform, setter_target, setter_platform, is_overridable, is_public, _, getter_prop, setter_prop] = prop_targets->second; @@ -3488,6 +3512,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, @@ -3933,7 +3961,16 @@ private static global::System.Runtime.CompilerServices.ConditionalWeakTable(get_type_semantics(evt.EventType()), typedef_name_type::Projected, false), From ed2ebda20888a9c475415c4f070e966350803812 Mon Sep 17 00:00:00 2001 From: "Chris Wall (WIN SDE)" Date: Wed, 4 Mar 2026 16:16:44 -0700 Subject: [PATCH 5/9] Skip code generation for removed types in ABI/interface/delegate/struct/class paths When a WinRT type has DeprecatedAttribute with DeprecationType.Remove, CsWinRT now properly skips generating code for it in all ABI code paths: - write_interface: Skip projected interface definition for removed interfaces - write_abi_interface / write_abi_interface_netstandard: Skip ABI implementation - write_static_abi_classes: Skip static ABI helper classes - write_abi_class: Skip ABI class code - write_abi_delegate: Skip ABI delegate code - write_abi_struct: Skip ABI struct marshaling code - write_winrt_exposed_type_class: Skip WinRT exposed type class - write_winrt_implementation_type_rcw_factory_attribute_type: Skip RCW factory Previously, the ABI code for removed types would reference projected types (e.g., RemovedClass, IRemovedInterface) that were correctly omitted from the projection, causing CS0234 compile errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/cswinrt/code_writers.h | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 35b544f93d..aaaf252a92 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -3700,6 +3700,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) @@ -7466,6 +7471,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); @@ -7488,6 +7498,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)); @@ -7704,6 +7719,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()) { @@ -8178,6 +8198,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); @@ -8997,6 +9022,11 @@ global::System.Collections.Concurrent.ConcurrentDictionary 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); @@ -9940,6 +9980,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)); From b4ba8896ef6a6ebba18a57cb656ea4ba739a8f49 Mon Sep 17 00:00:00 2001 From: "Chris Wall (WIN SDE)" Date: Wed, 4 Mar 2026 16:22:37 -0700 Subject: [PATCH 6/9] Fix property/event deprecation: check getter/add method, not metadata row MIDL places the DeprecatedAttribute on the getter/add accessor methods, not on the Property/Event metadata rows. The previous checks using is_removed(prop)/is_removed(evt) always returned false because those rows never carry the attribute. Fixed in write_class_members: - Property collection: check is_removed(getter) instead of is_removed(prop) - property_deprecation map: store getter MethodDef for write_obsolete_attribute Fixed in write_class_event: - Check is_removed(add) instead of is_removed(event) - Use write_obsolete_attribute(w, add) instead of write_obsolete_attribute(w, event) Fixed in write_interface_member_signatures: - Property loop: check is_removed(getter) and is_deprecated_not_removed(getter) - Event loop: check is_removed(add) and is_deprecated_not_removed(add) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/cswinrt/code_writers.h | 40 ++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index aaaf252a92..155ad70753 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -1545,7 +1545,9 @@ 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 = {}) { - if (is_removed(event)) + auto [add, remove] = get_event_methods(event); + // MIDL places DeprecatedAttribute on the add method, not the Event row + if (is_removed(add)) { return; } @@ -1562,11 +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, event); + 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); @@ -3317,7 +3318,7 @@ visibility, self, objref_name); { std::set writtenInterfaces; std::map>, std::optional>>> properties; - std::map property_deprecation; + 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) @@ -3372,12 +3373,13 @@ private % AsInternal(InterfaceTag<%> _) => % ?? Make_%(); // Merge property getters/setters, since such may be defined across interfaces for (auto&& prop : interface_type.PropertyList()) { - if (is_removed(prop)) + 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; } - MethodDef getter, setter; - std::tie(getter, setter) = get_property_methods(prop); 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()); @@ -3395,7 +3397,12 @@ private % AsInternal(InterfaceTag<%> _) => % ?? Make_%(); ); if (inserted) { - property_deprecation.try_emplace(property_name, prop); + // 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) { @@ -3988,17 +3995,18 @@ private static global::System.Runtime.CompilerServices.ConditionalWeakTable Date: Wed, 4 Mar 2026 19:27:25 -0700 Subject: [PATCH 7/9] Add regression tests for property/event/ABI deprecation gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand the test IDL and run_test.ps1 to cover all gaps found during functional testing against a real WinRT IDL: Test IDL additions: - Properties (normal/deprecated/removed) on [default_interface] runtimeclass - Events (normal/deprecated/removed) with TestEventHandler delegate - Deprecated and removed interface types (IDeprecatedInterface, IRemovedInterface) - [default_interface] on TestClass and classes New regression tests (Steps 9-13 in run_test.ps1): - Step 9: Property deprecation/removal on projected class (Regression: MIDL puts DeprecatedAttr on getter, not Property row) - Step 10: Event deprecation/removal on projected class (Regression: MIDL puts DeprecatedAttr on add method, not Event row) - Step 11: Interface member signatures filter removed members and emit [Obsolete] on deprecated methods, properties, and events - Step 12: ABI code integrity — no WindowsRuntimeHelperType or RcwFactoryAttribute references to removed types - Step 13: Compilation test — compiles all generated C# code with dotnet to catch any ABI reference to missing projected types Code fixes in code_writers.h: - write_method_abi_invoke: Generate E_NOTIMPL stub for removed methods (vtable slot preserved for ABI compat, but CCW returns error) - write_property_abi_invoke: Generate E_NOTIMPL stubs for removed property getter/setter (vtable slots preserved) - write_event_abi_invoke: Generate E_NOTIMPL stubs for removed event add/remove (vtable slots preserved) All 40 regression tests pass, including compilation verification. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/cswinrt/code_writers.h | 80 +++++++- tests/DeprecatedRemovedTest/.gitignore | 1 + .../DeprecatedRemovedTest.idl | 111 ++++++++--- .../DeprecatedRemovedTest.winmd | Bin 4608 -> 6144 bytes tests/DeprecatedRemovedTest/run_test.ps1 | 178 +++++++++++++++++- 5 files changed, 337 insertions(+), 33 deletions(-) create mode 100644 tests/DeprecatedRemovedTest/.gitignore diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 155ad70753..b9205e5498 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -6649,6 +6649,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_%% @@ -6695,6 +6712,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) @@ -6806,10 +6857,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(); 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 index 39c9a2f626..24805925a5 100644 --- a/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl +++ b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl @@ -1,9 +1,19 @@ // 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 @@ -36,6 +46,46 @@ namespace DeprecatedRemovedTest 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 { @@ -47,21 +97,33 @@ namespace DeprecatedRemovedTest void AnotherNormalMethod(); }; - // Deprecated delegate - [deprecated("DeprecatedDelegate is deprecated.", deprecate, 1)] - delegate void DeprecatedDelegate(); + // Deprecated interface - should have [Obsolete] but still be present + [deprecated("IDeprecatedInterface is deprecated.", deprecate, 1)] + interface IDeprecatedInterface + { + void DeprecatedWork(); + }; - // Removed delegate - should NOT appear in projection - [deprecated("RemovedDelegate is gone.", remove, 2)] - delegate void RemovedDelegate(); + // 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(); + }; - // Normal delegate for comparison - delegate void NormalDelegate(); + // ======================================================================== + // RUNTIMECLASSES + // ======================================================================== - // Class with mix of active and removed methods + // 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(); + + // --- Methods --- void ActiveMethod(); [deprecated("RemovedMethod has been removed.", remove, 2)] void RemovedMethod(); @@ -70,10 +132,25 @@ namespace DeprecatedRemovedTest 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; }; + + // --- 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(); @@ -81,24 +158,12 @@ namespace DeprecatedRemovedTest } // 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(); } - - // 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; - }; } diff --git a/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.winmd b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.winmd index 1e30ba3eb0a2fb49a43de27800f84e3e244a34b5..7bfac2be050ef1d640bba737ff79edb3bf78961e 100644 GIT binary patch literal 6144 zcmd5=4Qx}_6+ZVp+ld27Z~`nWP@I^QpVANt?S@KONC+k6FOZOwmI5#KBQbSsWWR); zt!q$Px^{Fe?S8ZYD_f>@rqVXrZK!C{*yuv1RsB(!R8Y01OGQ@+iEW}{D~Rno=RNz` z6f&ks)1LJ{-}7_Nx#ygF&wKXD`{B3hvo{q4gaMKjW2(yGR&F&Xk2 z-}OY}z}AR-47f7iRA=*9A#L$me)PK3shSqx;lw)N)Y;{EAk;L}Y+TOSo?Jd$mP6Q9(~<9cu`H{<3KVG7N~U4>i6{TbY>y8$EMapa8nEGCK8o258LFMQfEj@64w*%Zg@r7;f0QP&`owv6LuN*9|P zKU1&^51a?43dj$NGjSIvzfhc!yHGzreXpK~GsC;VrZ_`P6^o2B#Z(C$f+lB-sZyah zPfS&cKhD=fpr(kd2o%d^i;VNfvRg&Qd1Tofk#Rm*R!7Qrl9p3@{~NRl_y=Hya~^xD zz5k-%6<~%xNxcgGRA7cPSG@+#?LZ%$Qp+8LJLG70#X$<8uiDg*f0vr6c;C9R3}wr3 z9CBnC%9a)6$ubnno>6jMARqExfCrA1uOY9}a;dUBDwsz#=TTeps2B67d@Fe#ihrfG zNQK0E@hMQ5=f_a-hoHvFy0Eh1i`rqmI7>~?kLY~1`5G#op&!<{Q}8uZybv-z%X|$L zw?bBK6QClHabMtT$a`9!sknjjHRL^)rF7bZI%H%zomCu>t;rNeaiOS&Ac?l%+VL$}GhZP0msr(M?&3Bbu6}IHD?t`X-&$>%7%j>RguM_$oa! zRYqpVm!ZxoS9YVCielKN8l`~$evDo~M`T;SuJQ76u|Q{Ov0#C0?;GLdTCm4AGAmyv zK zCaz?fxPoco%B6`bmL{&0AJ&Q}0G#iw0>->^fnV~TCa%Cof`#a-YjKaI4&ZnhSIBrW zo;`&$O*k`zQzzs37}wA(zR!QX^U1NR_e4iH51}WtYX)n+Kkaa@Zvx58SPH!VLw|N?&xgS_b_XAmH zGdOK}SomS#gk@G(tcAr|Kco-q10o&3+>@RGSbG;(P9E=|gY0)ubOy!3pja4&^r%kg zQ%Ch6a73>c><7;9356xfRAx@VE zoH9J~$I)l;v@66}&ocHt-{nPot4K98vW;qxuc;ze06Rih?C$Mm3BGMZ(7ts=PTha_0mKj-V_9Xhu zNSm3muFb989*y@zs3#RpBxBJ|YTcQ#%pPir_x6x8S;o!_b?wqbZ#-gH(L_A+j-A7* zU1?edqz1K{$rN-JTX?nZ?6pj)4O@w1b_o!*?c8Q&X*1$#_8GBWbJHeT9NX4yPy;Md zi;=XVMl7~7cSY^dRJ1c@(z0kIV#cAKN@Q4W{Id3?(ImoeHN%N`gjOYzJw{CUtK%lM zZBNk3-k22)8!2mv88f?(ww!fkS$FqFa~7HC+-im`>M^bEM1-2-eTfInOun*HY*$&5 zi_p|(#;s*WJQ6dLn4L-)T_&}2#8>45ze#%GKtvhC4zIc0G7z}b)OLZi^D^A99fp%G zhguv@SlwnaZ%VT}r#Wt!$t^}WH~Nl5@K(MG`{Oj&Q6u740+I z5^hZ;?6qcBG-V}a^Ryvfs9D&8qO-g%)7oUOttFY*MzRfvneGxrz$|A0LK~5YGf%vb zC8oQb94F-ffN!F7SgQF&FSIf3!tJ&i`bIsX=wpz$l zIOU|trt&MB$IH&6oT}QDab~$|u-Tc)+LW^jn^AA z7LE(Ttz}pCFJAbXg1f3DI7ayk3cSuWUeTp}tJ}-wUR?9__y2O>xw0QVNrW|5xh^uT zCErbsX>U+dlPi!v1#9DtS!X7u&b2(-_WNZo-FajVQH`PsCqucWx)O0SCpnl}d-(OQ zM*j5MO~;y^`u?k*TVF>6=A+f`@6+DCwEnrVuU($g-*^eBLmf(HE1K#q(e7E-T_7tv zD{wSNsShB1ayg>~g`c*{hlWtL-j9tq{o3X~ g_4KiuBr2!^{ZFFDa-+llmO_66wEcgOF#iMi7iLMjg#Z8m delta 1713 zcmah~ZD?Cn7=F(=H(8UVY0_%ew3`b{JN(3=GABc5n$@l?!fIEG3L|ZIam8-SwoEMI zZX~EHY+yb3fwb6CCxTj#A^30AU<8Na$DiSlse!?8f5d{FPVsr)qzwhp3+K7#d7k&Y z=RN1#k9j3-dcJv<&L}=tKE+a?1dmUXS;bpr70*}kysBBH)q!~Gy*m4};54^mcXm88Fc ztc*d~tH-K+*7Hb_)CJWKx1gS&fKw2+pa$t%Q$WFSXAUtdv;e%Q7m)1(AZJ@Zwl{Q9 z^rA!-MfZx5t|;k#gzf4wFlEXZ90&5?G6p-wXr91mc8zA;XfDNQE+r{S67C_>2~6rV z@J*8zJ%j!V%>iA#1iYxpFeYxrEoCx-A$%k2DMW2Z8lvqK#qoDwYa_ISw%BNW*SVqk zohsh@Xl`V5!rkW{&r#2b@dK$GKGvfra<1)ZNZB*tcDoe5&wkO+9!ibn#-DZ{c5?@h z9I!3Xmu!8*-F7~DI*zwAAupNF{b<|Hx~F>1_GeH1=3HB}yBmLwx!-ut{&~8zf9Ab| zceYJjy)Mzp_T9A4Zg1+bN1FzlFZJJk{jcWB1NmLkL$Ag!?8TQmp;TkacHR8Qx#8Fr qWiK^79>ga=`4gJ;lbh~o;gINab&7uxxcp4>FMu06qTaGuHSrHzRU(c6 diff --git a/tests/DeprecatedRemovedTest/run_test.ps1 b/tests/DeprecatedRemovedTest/run_test.ps1 index d905969098..e95aa82585 100644 --- a/tests/DeprecatedRemovedTest/run_test.ps1 +++ b/tests/DeprecatedRemovedTest/run_test.ps1 @@ -1,6 +1,13 @@ # 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" @@ -63,7 +70,7 @@ Test-Result (Test-Path $outputFile) "C# projection file generated" $content = Get-Content $outputFile -Raw # ============================================================================ -# Step 3: Verify REMOVED types are excluded from user projection +# Step 3: Verify REMOVED types are excluded from projection # ============================================================================ Write-Host "`n=== Step 3: Removed types excluded ===" -ForegroundColor Cyan @@ -84,6 +91,11 @@ Test-Result (-not $hasRemovedClassDef) ` 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 # ============================================================================ @@ -101,6 +113,9 @@ Test-Result ($content -match 'Obsolete.*DeprecatedDelegate is deprecated') ` 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] # ============================================================================ @@ -134,13 +149,6 @@ Test-Result ($content -notmatch 'Hidden =') ` # ============================================================================ Write-Host "`n=== Step 7: IVtableTest vtable preservation ===" -ForegroundColor Cyan -# The ABI vtable must have all 4 methods including RemovedMethod -# Check for RemovedMethod in the ABI section (should exist for vtable) -Test-Result ($content -match 'RemovedMethod') ` - "RemovedMethod exists in ABI vtable code (binary compat)" - -# But user-facing interface should NOT have RemovedMethod -# The user interface IVtableTest should have NormalMethod but NOT RemovedMethod in its declaration Test-Result ($content -match 'NormalMethod') ` "NormalMethod accessible in projection" @@ -151,7 +159,7 @@ Test-Result ($content -match 'Obsolete.*DeprecatedMethod is deprecated') ` "DeprecatedMethod has [Obsolete] annotation" # ============================================================================ -# Step 8: TestClass method-level removal +# Step 8: TestClass method-level removal and deprecation # ============================================================================ Write-Host "`n=== Step 8: TestClass method-level removal ===" -ForegroundColor Cyan @@ -162,6 +170,158 @@ Test-Result ($content -notmatch 'Obsolete.*RemovedMethod has been removed') ` 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: Compile generated C# code +# (Ultimate regression test — catches any ABI reference to missing types) +# ============================================================================ +Write-Host "`n=== Step 13: 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 + # ============================================================================ # Summary # ============================================================================ From 9e2805e5dc034dc422314029e012577cb8880f24 Mon Sep 17 00:00:00 2001 From: "Chris Wall (WIN SDE)" Date: Wed, 4 Mar 2026 19:51:32 -0700 Subject: [PATCH 8/9] Fix removed statics/constructors and expand regression tests Bugs fixed in code_writers.h: - write_static_method: Add is_removed(method) check to skip removed static methods from user-facing projection - write_static_members: Add is_removed(getter) check on static property loop to skip removed static properties (MIDL attribute on getter) - write_factory_constructors: Add is_removed(method) check to skip removed constructor overloads from projected class Test IDL expansion (DeprecatedRemovedTest.idl): - Read-write properties: WritableProp, WritableDeprecatedProp, WritableRemovedProp (tests setter path) - Static properties: StaticProp, StaticDeprecatedProp, StaticRemovedProp (tests static property removal) - Constructor overloads: TestClass(String) deprecated, TestClass(String, Int32) removed Regression test expansion (run_test.ps1): 40 -> 53 checks - Step 13: Read-write property deprecation/removal - Step 14: Static property deprecation/removal - Step 15: Constructor deprecation/removal - Step 16: Interface checks for new constructs (WritableProp) All 53 regression tests pass including compilation verification. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/cswinrt/code_writers.h | 13 +++ .../DeprecatedRemovedTest.idl | 20 +++++ .../DeprecatedRemovedTest.winmd | Bin 6144 -> 8192 bytes tests/DeprecatedRemovedTest/run_test.ps1 | 75 +++++++++++++++++- 4 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index b9205e5498..4b1fd3a628 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -2144,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) { @@ -2491,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); @@ -2538,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()), diff --git a/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl index 24805925a5..64fcd52048 100644 --- a/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl +++ b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.idl @@ -123,6 +123,12 @@ namespace DeprecatedRemovedTest { 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)] @@ -140,6 +146,20 @@ namespace DeprecatedRemovedTest [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)] diff --git a/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.winmd b/tests/DeprecatedRemovedTest/DeprecatedRemovedTest.winmd index 7bfac2be050ef1d640bba737ff79edb3bf78961e..6f56ca4ac46657398dde38c40341946b59688229 100644 GIT binary patch literal 8192 zcmeHMeQ;FO6+iFozRfO}kZh0-`C14Of)KtD6f`6RSrv&855Z|pH)YlowXuK)K8ikh`mH>psD5(Rp5U zdPMTN;JbngRJL=AU^~!5JJo8~*`>1IgX(Q)y~oWub2P?E!4AQ_f)5M+062_Z(74x0 zpof01v7Prt@()43hg+uz&J|oOxCzL`96!&jvYcf68rOyKs5SUE8INAW+?kBCpwV!P zaUM#kz+#*UW-hV98DR#y@W7d2mPbA?&do9~pD@nRGR&xt9;qPWT=Dua8Rv{yzO-@f zm=#brG&zUNMhN4~F`F!mGsx_6@y?mN4{W-$aYnhVQrbAP+*T!RoMCROmNw2bx2+__ z^9tQSb)6^ZCg87u8OevV36isd=YbjdSmhYxlYkk?T;)|r76LtVO1Z&8xI@NvjyX&v zICo9f>$_7aQ+Qq5Z5d{^;UIOiWtiE9t>kFSFmBtg$bLd=ul5N%@L0L@YKrP){u~y_ zVZj`>Er&go!*Z?Uc*y^$TI0%k&p!p0xnFwo{{VK085s5Ep9Ra$GOugDg5yO>Z~j4!_8!o-t zjoM;ul1acCv{r4hFcYUKh*1F1S~Yjf;_FZ1SKp zb+&V>94^OIv#wwity-qet)|4;%UNelr?m^~7ZclsIvG2rFF&ov4gUo^8EmJqbQt$n z8E_130RD=e2mV!6l*3e}_<;MBu~JVH?~^p~{zwz=i!||mNE7daH1YaR6R-O;@p?}a zuk$qV`c4zC>ooCtP7|-=H1YaP6R+De@p?@YuhTT~`kV}p-}EpdHmNTG=H&%}qx0S- zUQ-_ldU4mT#pe>L2ac5b7^#oP6N(p4Gu%2|By*&`0QFKl3Dr{t71JlU$%f-jtU^72 zdyM5_k?2zIguGJe0`8+OkslF!36citk$Mm6pP)XD-lEgMS;}dVpO(HZybFvVmoko; zl@j2WfGnv{ipir^0l%SCi6ki40Lgqc418G$izF)8W#KsbNV!ky-O!1tN2GoPmg`Yx zNm?XFQGZJ9f&4Jgth3)=Nb70HPphYu5;_lL`Fnz-7E_7KrH-TRZkH;NtE}UbdH}5+ zwL~N(&?(VML|!IXrT&>F=2fYGqypec`i`ecJx#v>vYjgR4Pa2}4X{(|3QIi-i zJmw-7V*vQ3t3t3FIKh2Xun!n?6K=eqy8^h!-3ffj-3|QMeH3_&)(7m+NaK0{*r9a- zOI+PRkJ<;Ub&-ch(ExnR)hXBweA9JQFp$R{D+C*WL3gL%QQ#godD&wC__4bJh@1#^ z0~Ypgj@ z(3G1xrap;|)6RMKLwJ=a))*%Oy)*$QU?E-)Rs0p>X7plzxFH!2R1pt9D^J3+T_JWK zzXgv(l!bH^q+VKrkAic_2l;5Am##$og|rx`;Pe^}`K7=@T8jPerK^D|PPBZqUItXL z`$qsvs0dgJ|0*p+4}}&9&VyBz<_gY7KZOQ@!h)Y7u;8byu;8a=Sn$&}u@IwasK==c*h*Ib zlQauxz`q}FW^;je(0pX%UU(iwd*IpKN%T+a522v&d}bx6E64?xtW30S6B%IX>O`b1 zre8xV^wy*v4jFo6t=^Jo*CVxh${1K$-Vu$rM5rYdP9$T|Mq0N$W#}zb9dBzPtFzRt zm)-J}iMDtoWJDA3%=@oHRK8j_LTC+9ot{iVXSspb-o`dVr}D6oNM=JoQ|9KDF`ZULBN06g^;9Awvd1s6uZ$)! z{B?Rb5s%PKiDXMCCh|3LooYK0w7M;3M8lz!u|kjOO-P#~uB_;~wx}b>MB_F+Y*34C zG$$eyjJGFl*E9Lbb}?OLDNjOmyB;@Ih2oKzo<#3dD%7M?9gnzQKJa@%8yrYe#;_#| z))^rTT;|ktfwXfN_OLC1l`e}dk0*>~J(<%bX!i-m4L!Lv6n2ijK9Rhgf|eOu!4jCQ z97k0wluD5dq56(+tSuF7*J~5@oXW7*>P^v=jb^5+1AYnO!HtG`ldaJva%>@U8(2fMLovmfZDcYl5RnQ9Kj@I$0*Rhw4o&7t;aB5AAEHX{QOXyQ)M zW0a+~23~~&a`R~Q?GHWr;>tA#i$DGN(EW#xllQqhH`R?@(089ZpeQ~szxQy-3%Hbm zq9Wf6g(hTH46d0#b0`&P)b)5EXkc{6#4i$h}?Il4nn(r=9Ag-+`Fbu9!`B3SCKm*TE55uu%_<`mI=634 zoqz7xo4@w9XHJd1_c#0k zp5xBk6N9=d!4se5&eptexWfM;WjEED^P=SpIDjLy!2K9i1<&RgY~t{J_$-;JC(n@XJ54?B6goXWW)i zK3K}_(?yb#JKiCjVVM;0p0Ov7kHJAl=-6JOH-zPR3YBIK{)@Ek>|%}J^SpQhN>Rwa zgws~NjlIvs8k+81i!&Dq%DEO_e0CRghaIEIppS-eh&#C$<$rDh~&wH&x^9JsSN05MdSq*hmBBm#19DMZNU3YKVbL!ef#)l(u zu3<5i+DWiRC#%_(ns*rG&a4Or4H>?+w@=x2(}}k>?VJDEm;01xH_){cvo)Ih(Onvmib@q>j#iUw9vghEm31CiqY%qH7d5ISLg z_q^tuJ#*(y60t^pU;;?RegRe#+JtWbM9MsGJ)X+Kss#Q(8MdUs8pHx)!t0u*UEn!x-qH$E*%2q+aVdIB58);k$+(8Gd27VCfob z>2XfVzc)* z=M?wLV=+*tGKx2c$6}xvKJJglVxR}UP)>n%6h7W79*g#}su$%s1=_W|rs&OzNm}k|NUgJv4q1{7P1RSD{tpwLfJ>RpvErDV*21rK-Hf zExFtBD)&^K*SIHs=Un1LT2@VVO(1@&2WWw=GS)T|P01qwIk9+S|b)dpG!+y-eKOFQA_DKj7X?{S>1R-Jmw|=Zq}WU6aGqAe8Xy zV?{gN0TH4-GA_7&9P)xl8aXM#^sAVHWk{wV_k$dhGL|{;yqt%95@h?lv1DP%%B-u1XyRSfKf#jcMt=6)d1+KIdE337?RDaB8CHCos|G5tU1FK(6S?*kc&qN zj<&;>Bz>axfu9*(FudJzEB1sc!IHNOKQO%A;>8s$alci75(hv*E=L_N&awqdDcR%( zX$x*(FI9q)HcgM8*H7q z9{fw{mo`o7uCgP(-ocU4wxMT7M)Y&#?fP=L4j%e4;QhP!+1_UB(EFqJcO8A6{?Wr*KG$!Dhk{Gz4!`l}=$`(}52@IXH#0@rs@kg?w_f(+ iQW2hr6s1r3bl0}~%HHz|bjHII5yDBFhOWjh?)?{CuN&Y1 diff --git a/tests/DeprecatedRemovedTest/run_test.ps1 b/tests/DeprecatedRemovedTest/run_test.ps1 index e95aa82585..2b6959f681 100644 --- a/tests/DeprecatedRemovedTest/run_test.ps1 +++ b/tests/DeprecatedRemovedTest/run_test.ps1 @@ -274,10 +274,81 @@ Test-Result ($content -notmatch 'RemovedClassRcwFactoryAttribute') ` "No RcwFactoryAttribute for RemovedClass" # ============================================================================ -# Step 13: Compile generated C# code +# 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 13: Compile generated C# code ===" -ForegroundColor Cyan +Write-Host "`n=== Step 17: Compile generated C# code ===" -ForegroundColor Cyan $compileDir = "$testDir\compile_test" if (Test-Path $compileDir) { Remove-Item $compileDir -Recurse -Force } From 014a661cade0d0b70285da9410d3113279cb3b07 Mon Sep 17 00:00:00 2001 From: "Chris Wall (WIN SDE)" Date: Thu, 5 Mar 2026 13:27:27 -0700 Subject: [PATCH 9/9] Skip fully-removed classes in component activation factory and add regression tests - main.cpp: filter removed types from componentActivatableClasses - code_writers.h: early return in write_factory_class for removed types - run_test.ps1: add Step 18 component factory exclusion test (57 tests total) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/cswinrt/code_writers.h | 4 ++++ src/cswinrt/main.cpp | 1 + tests/DeprecatedRemovedTest/run_test.ps1 | 30 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/cswinrt/code_writers.h b/src/cswinrt/code_writers.h index 4b1fd3a628..77d28d07d5 100644 --- a/src/cswinrt/code_writers.h +++ b/src/cswinrt/code_writers.h @@ -10404,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); 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/run_test.ps1 b/tests/DeprecatedRemovedTest/run_test.ps1 index 2b6959f681..7ed0c27f9c 100644 --- a/tests/DeprecatedRemovedTest/run_test.ps1 +++ b/tests/DeprecatedRemovedTest/run_test.ps1 @@ -393,6 +393,36 @@ if (-not $buildSuccess -and $hasErrors) { # 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 # ============================================================================