Skip to content

User/chriwall/deprecated - #2376

Open
Dreynor87 wants to merge 9 commits into
masterfrom
user/chriwall/deprecated
Open

User/chriwall/deprecated#2376
Dreynor87 wants to merge 9 commits into
masterfrom
user/chriwall/deprecated

Conversation

@Dreynor87

Copy link
Copy Markdown

Chris Wall (WIN SDE) and others added 9 commits March 2, 2026 14:58
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>
- 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>
- 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>
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>
…ct/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>
… 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>
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>
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>
…gression 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>
@Sergio0694

Copy link
Copy Markdown
Member

Ported to 3.0 in #2457. Manodasan Wignarajah (@manodasanW) let me know if you want to just close this or consider taking it too.

Sergio Pedri (Sergio0694) added a commit that referenced this pull request Jul 27, 2026
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>
Sergio Pedri (Sergio0694) added a commit that referenced this pull request Aug 1, 2026
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>
Sergio Pedri (Sergio0694) added a commit that referenced this pull request Aug 12, 2026
* Add deprecation helpers and [Obsolete] emission to the projection writer

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>

* Skip removed APIs and annotate deprecated members in the projection

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>

* Stub removed interface members with E_NOTIMPL in the CCW vtable

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>

* Support [Deprecated] for members of authored components

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>

* Add authoring test coverage for [Deprecated] members

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>

* Refactor DeprecatedAttribute parsing patterns

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.

* Stub removed authored members with E_NOTIMPL in component mode

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>

* Don't reference removed members from the authoring consumption test

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>

* Return E_NOTIMPL when a type's default constructor is removed

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>

* Skip removed factories when deciding ref-mode constructor emission

'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

* Add test coverage for deprecated and removed constructors

'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

* Suppress obsolete diagnostics in generated projection code

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

* Suppress the obsolete diagnostic in the PlayToManager interop test

'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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c7bf41f2-a686-4f26-ae40-a5a21d432ede
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants