Skip to content

Support [Deprecated] in 3.0 projections and authored components#2457

Open
Sergio0694 wants to merge 9 commits into
staging/3.0from
user/sergiopedri/deprecated-attribute2
Open

Support [Deprecated] in 3.0 projections and authored components#2457
Sergio0694 wants to merge 9 commits into
staging/3.0from
user/sergiopedri/deprecated-attribute2

Conversation

@Sergio0694

@Sergio0694 Sergio0694 commented Jun 19, 2026

Copy link
Copy Markdown
Member

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 marked DeprecationType.Remove are omitted from the projected surface while their ABI vtable slot is preserved (stubbed to return E_NOTIMPL) for binary compatibility. The same attribute is supported when authoring Windows Runtime components.

Motivation

Honoring [Deprecated] was supported in CsWinRT 2.x (in cswinrt.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, and Remove (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: adds IsDeprecated, IsRemoved, IsDeprecatedNotRemoved, and DeprecatedMessage helpers that read the [Deprecated] attribute arguments (message and DeprecationType).
  • Factories/CustomAttributeFactory.cs: emits [System.Obsolete(message)] for members and types that are deprecated but not removed.
  • Generation/ProjectionGenerator.Namespace.cs and Generation/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, and Builders/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 its add accessor.
  • Models/PropertyAccessorState.cs and Models/StaticPropertyAccessorState.cs: track the accessor that carries the deprecation marker so the [Obsolete] attribute is emitted on the projected property.
  • Factories/AbiInterfaceFactory.cs and Factories/ComponentFactory.cs: a removed member keeps its CCW vtable slot but the slot is stubbed to return E_NOTIMPL in 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 (its ActivateInstance returns E_NOTIMPL instead of emitting new T()). The vtable slot is preserved so the layout stays stable for existing native callers.

WinMD generator (authoring side)

  • Writers/WinMDWriter.Members.cs and Writers/WinMDWriter.Attributes.cs: when generating a component .winmd, the [Deprecated] attribute for properties and events is emitted on the accessor (the getter for properties, the add accessor 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: extends DeprecatedMembersClass to cover Deprecate and Remove across 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 throw E_NOTIMPL. The new members sit after the removed slots in vtable order and still dispatch correctly, proving the removed slots remain in place.

Sergio0694 and others added 6 commits June 18, 2026 15:32
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.
@Sergio0694 Sergio0694 added enhancement New feature or request authoring Related to authoring feature work CsWinRT 3.0 labels Jun 19, 2026
@Sergio0694 Sergio0694 requested a review from manodasanW June 19, 2026 00:10
Sergio0694 and others added 3 commits June 18, 2026 21:53
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

authoring Related to authoring feature work CsWinRT 3.0 enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant