Support [Deprecated] in 3.0 projections and authored components - #2457
Open
Sergio0694 wants to merge 13 commits into
Open
Support [Deprecated] in 3.0 projections and authored components#2457Sergio0694 wants to merge 13 commits into
Sergio0694 wants to merge 13 commits into
Conversation
This was referenced Jun 19, 2026
Sergio0694
force-pushed
the
user/sergiopedri/deprecated-attribute2
branch
2 times, most recently
from
July 28, 2026 05:53
f934152 to
cdad1ae
Compare
Port [Windows.Foundation.Metadata.Deprecated] handling from PR #2376 (CsWinRT 2.x) to the CsWinRT 3.0 projection writer. windows-rs and other consumers surface deprecated/removed WinRT APIs via this attribute. - Add IsDeprecated / IsRemoved / IsDeprecatedNotRemoved / DeprecatedMessage extension members that read the DeprecatedAttribute (the second fixed argument is DeprecationType, where Deprecate = 0 and Remove = 1). - Add CustomAttributeFactory.WriteObsoleteAttribute, which emits [System.Obsolete(message)] for a deprecated-but-not-removed member, and wire it into the projected type-level attributes (classes, interfaces, enums, structs, delegates). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A WinRT API marked [Deprecated(..., DeprecationType.Remove, ...)] is omitted from the generated projection; a merely deprecated API is annotated with [Obsolete]. - Skip fully removed types in the per-namespace generation loops (type-map attributes, projected types, ABI types, and generated IIDs). - Honor removal/deprecation on interface member signatures, runtime class members, static members, factory and composable constructors, enum fields, and the IDynamicInterfaceCastable forwarders. - MIDL places [Deprecated] on the property getter / event add accessor (not on the Property/Event row), so removal and deprecation are checked on the accessor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A removed member is omitted from the projected interface, but its native vtable slot must be preserved for binary compatibility. The CCW (Do_Abi) entry now returns E_NOTIMPL (0x80004001) for removed methods, property accessors, and event accessors, while the vtable layout (one function pointer per metadata method) is unchanged. The removal marker lives on the property getter / event add accessor (MIDL convention), so both accessors of a removed property/event are stubbed. Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
Authored Windows Runtime components can mark their own APIs with [Windows.Foundation.Metadata.Deprecated], including DeprecationType.Remove, just like the Windows SDK does. Two changes make this work end to end: - WinMD generator: emit the [Deprecated] attribute for properties and events on the accessor method (the getter for properties, the 'add' accessor for events) rather than the property/event row. This matches the placement MIDL produces for the Windows SDK, so the projection writer (and other consumers such as windows-rs) resolve member deprecation the same way for authored components and the Windows SDK. Methods and types continue to carry the attribute directly. - Projection writer: a removed member's CCW entry only returns E_NOTIMPL when consuming a Windows Runtime type, where a managed object implementing the interface cannot supply the omitted member. In component (authoring) mode the dispatch target is the authored class itself, which still defines the member, so the entry keeps dispatching to that implementation. This preserves the vtable slot and binary compatibility for existing native callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extend the DeprecatedMembersClass authoring component and its consumption test to cover [Windows.Foundation.Metadata.Deprecated] across every member kind (method, property, event) and both deprecation types (Deprecate and Remove): - AuthoringTest: add removed (DeprecationType.Remove) method and property, and events of each deprecation kind. Building the component exercises the WinMD generator (which now emits the attribute on the accessor) and the component projection (where removed members keep dispatching to the authored class). - AuthoringConsumptionTest: call the removed members to confirm their vtable slot still dispatches to the implementation (a removed member that incorrectly returned E_NOTIMPL would fail here), and subscribe to events of each kind. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Simplify and clarify parsing of DeprecatedAttribute arguments in IHasCustomAttributeExtensions. IsRemoved now directly pattern-matches the second fixed argument value (1) instead of binding to a local int. DeprecatedMessage was expanded from an expression-bodied property into an explicit getter with a guarded pattern check and a ToString() return. No behavior change intended; these edits improve readability and avoid unnecessary temporary variable binding.
A member marked [Deprecated(DeprecationType.Remove)] is omitted from the projected surface but keeps its ABI vtable slot. The previous approach made the slot dispatch to the authored implementation in component (authoring) mode, but that does not compile: the C# compiler treats a call to a [Deprecated(Remove)] member as an obsolete-as-error (CS0619), which cannot be suppressed with #pragma warning disable. Generated code therefore cannot call a removed member. Removed members now return E_NOTIMPL in both consuming and component mode, keeping the behavior consistent and the vtable layout stable for existing native callers (the slot is preserved, only stubbed): - AbiInterfaceFactory: the removed-member E_NOTIMPL stub is no longer gated on consuming mode, and the per-event ConditionalWeakTable is skipped for removed events (the stubbed accessors never use it, so it would be an unused field). - ComponentFactory: the activation/static factory class no longer emits forwarders for removed methods, properties, and events. The projected factory/static interface already omits them and their slot is stubbed, so the forwarder would only produce a call to the obsolete authored member. - AuthoringConsumptionTest: removed instance and static members, and the removed event, are now expected to throw E_NOTIMPL. The new members (which sit after the removed slots in vtable order) still dispatch correctly, proving the removed slots remain in place. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A member marked [Deprecated(DeprecationType.Remove)] is omitted from the projection, so consuming code should not reference it. The test no longer calls the removed members: it exercises the deprecated and new members, and relies on the new members (which sit after the removed slots in vtable order) dispatching correctly to confirm the removed slots are still preserved. This also avoids depending on the C++/WinRT projection's choice to keep removed members callable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A type whose parameterless constructor is marked [Deprecated(DeprecationType.Remove)] is no longer default-activatable: the activation factory previously emitted 'new T()', which does not compile because the C# compiler treats a call to a removed member as an obsolete-as-error (CS0619). ActivateInstance now treats such a type as non-activatable and throws (marshalling to E_NOTIMPL), consistent with how removed members are handled elsewhere. Parameterized constructors, if any, are unaffected and continue to activate through their factory interface. HasActivatableDefaultConstructor replaces HasDefaultConstructor (its only caller), returning true only for a default constructor that is not removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
'WriteAttributedTypes' skips factory interfaces and factory methods that are marked '[Deprecated(..., DeprecationType.Remove, ...)]', but the two predicates that mirror it in reference-projection mode did not, so a class whose activation or composable factory was entirely removed was reported as emitting constructors when it emits none. The synthetic non-public parameterless constructor was then skipped and the C# compiler synthesized an implicit public one in its place, putting a constructor on the reference surface that the implementation projection does not have (so a consumer's 'new T()' would compile and then fail at runtime with a missing method). 'EmitsParameterlessConstructor' now skips removed factories and overloads, and the inline 'hasRefModeCtors' loop in 'ClassFactory' is replaced by a new 'EmitsAnyConstructor' next to it, so both predicates stay in sync with what 'WriteAttributedTypes' actually emits. The per-factory-interface part is exposed as 'HasActivatableFactoryMethod' on 'TypeDefinitionExtensions', alongside the existing 'HasActivatableDefaultConstructor'. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c7bf41f2-a686-4f26-ae40-a5a21d432ede
'TestComponentCSharp' gains three runtime classes that cover the constructor shapes the projection has to handle: one with a deprecated constructor, a removed one, and a live one declared after it (so the removed factory slot has to stay in place for the live one to dispatch correctly), plus a sealed (activatable) and an unsealed (composable) class whose only constructor is removed. The unit tests assert both projections. The reference projection is checked at compile time with an overload pair whose 'new()' constrained member is only a candidate when the type really exposes a public parameterless constructor, which is what catches a class that dropped every constructor without emitting a non-public one in its place (the C# compiler would then synthesize an implicit public one that the implementation projection does not have). The implementation projection is checked at run time through reflection, and by constructing and calling the types. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c7bf41f2-a686-4f26-ae40-a5a21d432ede
Now that deprecated Windows Runtime APIs are projected with '[Obsolete]', generated projection code that references them fails to compile under 'TreatWarningsAsErrors' (CS0618). This is not a theoretical concern: the Windows SDK projection does not build, because 'ComInteropExtensions.cs' wraps 'IPlayToManagerInterop' in terms of the deprecated 'Windows.Media.PlayTo.PlayToManager', and the generated projection for 'WindowsRuntime.Internal.winmd' names it in the interop signatures. The '[Obsolete]' attribute is guidance for consumers of a projection, not for the projection itself, which has to name a deprecated type to project it at all. Generated projection files therefore suppress CS0612/CS0618, alongside the diagnostics they already suppress. The suppressions are per-file, so user code that calls a deprecated API still gets the warning. Three emission paths need it: the shared file prelude used by every per-namespace projection file, the centralized 'typeof'-based lookup classes (which name every projected type, deprecated ones included), and the hand-written 'ComInteropExtensions.cs' base resource, which only gets the auto-generated banner rather than the full prelude. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c7bf41f2-a686-4f26-ae40-a5a21d432ede
'Windows.Media.PlayTo.PlayToManager' is deprecated in the Windows SDK, so it is now projected with
'[Obsolete]'. 'ComInteropTests.TestPlayToManager' covers its interop extensions and therefore has to
name it, which breaks the Release legs (warnings are treated as errors there, which is why only they
failed).
This is the feature behaving as intended: unlike the generated projection code suppressed in the
previous commit, this is ordinary consumer code, and the warning correctly points out that it calls a
deprecated API. The test covers those extensions deliberately, so it acknowledges the diagnostic with
a narrow suppression at the call site, matching how the suite already handles the '[Experimental]'
('CS8305') and obsolete-override ('CS0672') tests.
'PlayToManager' is the only deprecated type this test class touches, and the compiler reported no
other obsolete usage across the solution build.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c7bf41f2-a686-4f26-ae40-a5a21d432ede
Sergio0694
force-pushed
the
user/sergiopedri/deprecated-attribute2
branch
from
August 1, 2026 20:10
f4bb9c0 to
f68b11e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Port
[Windows.Foundation.Metadata.Deprecated]handling to the CsWinRT 3.0 projection writer and WinMD generator. Deprecated Windows Runtime APIs are now projected with[System.Obsolete], and APIs markedDeprecationType.Removeare omitted from the projected surface while their ABI vtable slot is preserved (stubbed to returnE_NOTIMPL) for binary compatibility. The same attribute is supported when authoring Windows Runtime components.Motivation
Honoring
[Deprecated]was supported in CsWinRT 2.x (incswinrt.exe) but had not yet been ported to the 3.0 C# generators. Without it, deprecated Windows Runtime APIs are projected as regular members (no[Obsolete]guidance for consumers), and APIs the platform has removed would either reappear on the projected surface or shift the ABI vtable layout, breaking binary compatibility with existing native callers.The attribute carries a
DeprecationType:Deprecate(0) means the API still works but should be replaced, andRemove(1) means it is gone from the projected surface but its vtable slot must remain.[Deprecated]is not exclusive to the Windows SDK: authored components can apply it to their own members too, so support has to span both the consuming projection and the authoring pipeline.Changes
Projection writer (consuming side)
Extensions/IHasCustomAttributeExtensions.cs: addsIsDeprecated,IsRemoved,IsDeprecatedNotRemoved, andDeprecatedMessagehelpers that read the[Deprecated]attribute arguments (message andDeprecationType).Factories/CustomAttributeFactory.cs: emits[System.Obsolete(message)]for members and types that are deprecated but not removed.Generation/ProjectionGenerator.Namespace.csandGeneration/ProjectionGenerator.GeneratedIids.cs: skip removed types entirely so they never appear in the projection.Factories/InterfaceFactory.cs,Factories/ClassMembersFactory.WriteInterfaceMembers.cs,Factories/ClassMembersFactory.WriteClassMembers.cs,Factories/ClassFactory.cs,Factories/ConstructorFactory.AttributedTypes.cs,Factories/ConstructorFactory.Composable.cs,Factories/AbiInterfaceIDicFactory.cs, andBuilders/ProjectionFileBuilder.cs(enum fields): skip removed members (and removed factory/static interfaces) and annotate deprecated ones with[Obsolete], following the MIDL convention that a property's marker lives on its getter and an event's on itsaddaccessor.Models/PropertyAccessorState.csandModels/StaticPropertyAccessorState.cs: track the accessor that carries the deprecation marker so the[Obsolete]attribute is emitted on the projected property.Factories/AbiInterfaceFactory.csandFactories/ComponentFactory.cs: a removed member keeps its CCW vtable slot but the slot is stubbed to returnE_NOTIMPLin both consuming and component (authoring) mode. Generated code cannot dispatch to a removed authored member, because the C# compiler treats a call to a[Deprecated(Remove)]member as an obsolete-as-error (CS0619) that cannot be suppressed; the activation/static factory likewise omits forwarders for removed members, and a type whose parameterless constructor is removed is treated as non-default-activatable (itsActivateInstancereturnsE_NOTIMPLinstead of emittingnew T()). The vtable slot is preserved so the layout stays stable for existing native callers.Factories/ConstructorFactory.AttributedTypes.cs,Factories/ClassFactory.cs, andExtensions/TypeDefinitionExtensions.cs: the two predicates that decide whether a reference projection needs a synthetic non-public parameterless constructor now agree with whatWriteAttributedTypesactually emits, i.e. they skip removed factory interfaces and removed factory overloads. Without this, a class whose activation or composable factory was entirely removed emitted no constructor at all and the C# compiler synthesized an implicit public parameterless one in its place, putting a constructor on the reference surface that the implementation projection does not have (so a consumer'snew T()would compile and then fail at run time).ClassFactory's inlinehasRefModeCtorsloop is replaced by a newConstructorFactory.EmitsAnyConstructornext to the existingEmitsParameterlessConstructor, and the per-factory-interface part is exposed asHasActivatableFactoryMethodalongsideHasActivatableDefaultConstructor.Extensions/ProjectionWriterExtensions.cs,Factories/MetadataAttributeFactory.cs, andResources/Base/ComInteropExtensions.cs: generated projection files suppressCS0612/CS0618, alongside the diagnostics they already suppress.[Obsolete]is guidance for consumers of a projection, not for the projection itself, which has to name a deprecated type in order to project it at all — without this the Windows SDK projection does not build, becauseComInteropExtensions.cswrapsIPlayToManagerInteropin terms of the deprecatedWindows.Media.PlayTo.PlayToManager. The suppressions are per-file, so user code calling a deprecated API still gets the warning.WinMD generator (authoring side)
Writers/WinMDWriter.Members.csandWriters/WinMDWriter.Attributes.cs: when generating a component.winmd, the[Deprecated]attribute for properties and events is emitted on the accessor (the getter for properties, theaddaccessor for events) rather than the property/event metadata row, matching the placement MIDL produces for the Windows SDK. This keeps authored components consistent with the SDK so both CsWinRT and other consumers (e.g.windows-rs) resolve member deprecation the same way. Methods and types continue to carry the attribute directly.Tests
Tests/AuthoringTest/Program.cs: extendsDeprecatedMembersClassto coverDeprecateandRemoveacross instance methods, static methods, properties, and events. Building it exercises both the WinMD generator and the component projection.Tests/AuthoringConsumptionTest/test.cpp: exercises the deprecated and new members of each kind (method, property, event, static), and asserts that the removed instance, static, and event members throwE_NOTIMPL. The new members sit after the removed slots in vtable order and still dispatch correctly, proving the removed slots remain in place.Tests/TestComponentCSharp/: addsDeprecatedConstructorClass(a deprecated constructor, a removed one, and a live one declared after it), plusRemovedActivationClass(sealed/activatable) andRemovedComposableClass(unsealed/composable) whose only constructor is removed. BuildingProjections/Testgenerates a reference projection for them, and the unit test publish generates the implementation projection.Tests/UnitTest/TestComponentCSharp_Tests.cs: asserts both projections. The reference projection is checked at compile time via an overload pair whosenew()constrained member is only a candidate when the type really exposes a public parameterless constructor, which is what catches a class that dropped every constructor without emitting a non-public one in its place. The implementation projection is checked at run time through reflection, and by constructing the types and calling their static factory methods (the surviving three-argument constructor sums its arguments, so it only produces the expected value if the removed constructor's factory slot is still in place).