Skip to content

Project the Windows Runtime [Experimental] attribute as the .NET one - #2503

Open
Sergio0694 wants to merge 4 commits into
user/sergiopedri/deprecated-attribute2from
user/sergiopedri/project-experimental-attribute
Open

Project the Windows Runtime [Experimental] attribute as the .NET one#2503
Sergio0694 wants to merge 4 commits into
user/sergiopedri/deprecated-attribute2from
user/sergiopedri/project-experimental-attribute

Conversation

@Sergio0694

Copy link
Copy Markdown
Member

Summary

Custom-map Windows.Foundation.Metadata.ExperimentalAttribute to System.Diagnostics.CodeAnalysis.ExperimentalAttribute, with the CSWINRT3005 diagnostic id, in both directions (projection and component authoring). Also adds docs/attribute-projections.md, documenting every attribute CsWinRT translates rather than projects verbatim.

Note

Stacked on top of #2457, which is where the [Deprecated] support this builds on comes from. Only the last three commits belong to this PR.

Motivation

Windows.Foundation.Metadata.ExperimentalAttribute was projected as an ordinary Windows Runtime attribute type, and the C# compiler happened to recognize it by name and report CS8305 for its use sites. That is a hard-coded, single, non-actionable warning: it cannot be suppressed for one API without suppressing it for every experimental API in the SDK, it carries no link to any documentation, and it only works because the compiler special-cases that one type name.

C# 12 added [Experimental] as the first-class modeling of exactly this concept, and it is strictly richer: it carries a diagnostic id, a help link, and a message, so opting into an experimental API becomes a normal, per-id, greppable suppression. Projecting the Windows Runtime attribute as the .NET one is the same treatment IClosable, IReference<T> and every other Windows Runtime type with a .NET counterpart already gets.

The behavior around attributes is also currently only discoverable by reading the projection writer and the WinMD generator, which is why this adds a documentation page for it.

Changes

Projection (.winmd → C#)

  • src/WinRT.Projection.Writer/Helpers/MappedTypes.cs: custom-maps Windows.Foundation.Metadata.ExperimentalAttribute to System.Diagnostics.CodeAnalysis.ExperimentalAttribute, so the Windows Runtime attribute type is no longer projected.

  • src/WinRT.Projection.Writer/Factories/CustomAttributeFactory.cs: emits [Experimental("CSWINRT3005", UrlFormat = ..., Message = ...)] for every application. Windows Runtime metadata has no per-API diagnostic id (the attribute takes no arguments), so all experimental APIs share one CsWinRT-owned id. The emit loop is extracted into a WriteAttribute helper so this and the carried-over path share it.

  • src/WinRT.Projection.Writer/Factories/InterfaceFactory.cs: the same for the interface member level carry-over path.

  • src/WinRT.Projection.Writer/References/WellKnownDiagnostics.cs: new, holds the diagnostic id, message and url format.

  • src/WinRT.Projection.Writer/Extensions/ProjectionWriterExtensions.cs: generated projection files suppress CSWINRT3005 in their prelude, alongside the CS0612/CS0618 they already suppress. A projection has to name an experimental type in order to project it at all, so the marker is guidance for the consumers of a projection, not for the projection itself. Unlike CS8305, this is reported as an error by default, so the suppression is what keeps the Windows SDK projection building.

  • src/Projections/Directory.Build.props: drops the now-redundant CS8305 NoWarn (verified by building the full Windows SDK reference projection without it).

Emission stays reference-projection-only, matching every other metadata attribute: implementation projections are only ever loaded at runtime, and attribute blobs cannot be trimmed by ILLink or ILC.

Authoring (C# → .winmd)

Since the Windows Runtime attribute type is no longer projected, it has no projected form left for a component author to apply. Authored components use the .NET [Experimental] instead, and the generator translates it back, so the component looks the same to every other language projection as one authored in MIDL.

  • src/WinRT.WinMD.Generator/Writers/WinMDWriter.Attributes.cs: translates System.Diagnostics.CodeAnalysis.ExperimentalAttribute into Windows.Foundation.Metadata.ExperimentalAttribute. The diagnostic id, UrlFormat and Message are dropped, as Windows Runtime metadata has nowhere to carry them.

  • src/WinRT.WinMD.Generator/Writers/WinMDWriter.Members.cs: generalizes the accessor placement established for [Deprecated] in Support [Deprecated] in 3.0 projections and authored components #2457, so both attributes land on the accessor method for properties and events (the placement MIDL produces), rather than duplicating it.

Documentation

  • docs/attribute-projections.md: new, one table per direction covering every attribute CsWinRT translates rather than projects verbatim, including which projection assemblies each result lands in. The two asymmetries easiest to get wrong are called out explicitly: the metadata attribute types that are not projected as types at all, and the fact that [Obsolete] on an authored component is not translated into [Deprecated], so it is invisible to every other language projection.

  • docs/diagnostics/cswinrt3005.md: new, the diagnostic page, including how to opt into an experimental API and why it is an error rather than a warning.

  • README.md, docs/authoring.md, .github/copilot-instructions.md: link the new pages and record the new diagnostic id.

Testing

  • src/Tests/ProjectionWriterTest/Test_MappedAttributes.cs: new, asserts the exact attribute text the writer emits (so the id, url and message cannot drift from the docs page silently), that it is reference-projection-only, and that neither the Windows Runtime attribute type nor any application of it survives.

  • src/Tests/WinMDGeneratorTest/Test_ExperimentalAttribute.cs: new, asserts the authoring translation for types, methods, properties and events. WinMDGeneratorRunner gains the ability to read attributes back out of the generated .winmd (via System.Reflection.Metadata, with Windows Runtime projections disabled so the raw metadata is asserted), which is what lets these tests assert placement, not just success.

  • src/Tests/UnitTest/TestComponentCSharp_Tests.cs: updates the existing CS8305 suppression to CSWINRT3005, and adds a test asserting the attribute does not survive into the implementation projection loaded at runtime.

  • src/Tests/WinMDGeneratorTest/WinMDGeneratorTest.csproj: drops Platform from the generator ProjectReference's UndefineProperties, matching ProjectionWriterTest. Without it, building with an explicit platform puts the tool under bin\<platform>\ while the tests look for it under bin\, so they silently run against a stale tool.

Verified by building the full Windows SDK reference projection (0 warnings, 0 errors, 40 projected [Experimental] applications and no leftover Windows Runtime ones) and running both generator test suites.

Sergio0694 and others added 3 commits August 1, 2026 13:10
'Windows.Foundation.Metadata.ExperimentalAttribute' was projected as an ordinary Windows
Runtime attribute type, and Roslyn happened to recognize it by name and report 'CS8305' for
its use sites. That is a hard-coded, single, non-actionable warning: it cannot be suppressed
per API, it carries no link to any documentation, and it only works because the compiler
special-cases that one type name.

The attribute is now custom-mapped to 'System.Diagnostics.CodeAnalysis.ExperimentalAttribute'
(the C# 12 modeling of the same concept), like every other Windows Runtime type that has a
first-class .NET counterpart. The Windows Runtime type is therefore no longer projected, and
every application is emitted as '[Experimental("CSWINRT3005", UrlFormat = ..., Message = ...)]'.
Windows Runtime metadata has no per-API diagnostic id (the attribute takes no arguments), so
all experimental APIs share one CsWinRT-owned id, which user code can suppress on its own.

Both carry-over paths are covered: the type level one in 'CustomAttributeFactory' and the
interface member level one in 'InterfaceFactory'. Emission stays reference-projection-only, so
nothing lands in implementation projections, matching every other metadata attribute.

Generated projection files suppress 'CSWINRT3005' in their prelude, alongside the diagnostics
they already suppress: the attribute is guidance for consumers of a projection, not for the
projection itself, which has to name an experimental type to project it at all. Unlike 'CS8305'
this is reported as an error by default, so the suppression is what keeps the Windows SDK
projection building. It also makes the 'CS8305' 'NoWarn' in the projections build redundant
(verified by building the full Windows SDK reference projection without it).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ed5549ac-fb33-4d08-a8f3-d6cf3b8bc8e1
… metadata

Now that 'Windows.Foundation.Metadata.ExperimentalAttribute' is custom-mapped, it has no
projected form left for a component author to apply, exactly like 'IClosable' or 'IReference<T>'.
Authored components use the .NET '[Experimental]' attribute instead, and the WinMD generator
translates it back into the Windows Runtime one when emitting the '.winmd', so the component
looks the same to every consumer (C++/WinRT, windows-rs, ...) as one authored in MIDL.

The diagnostic id, url format and message are dropped: Windows Runtime metadata has nowhere to
carry them, and CsWinRT synthesizes its own when projecting the component back.

Placement follows what was already established for '[Deprecated]': Windows Runtime metadata puts
these markers on the accessor method rather than on the property or event row (this is what MIDL
produces). Both attributes now share that path, so the accessor handling is generalized rather
than duplicated, and an author does not have to learn that a marker silently behaves differently
depending on which kind of member it is applied to.

'WinMDGeneratorRunner' gains the ability to read the attributes back out of the generated
'.winmd' (via 'System.Reflection.Metadata', with Windows Runtime projections disabled so the raw
metadata is asserted). That is what lets the new tests assert placement, not just success, which
is the part that would otherwise silently regress.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ed5549ac-fb33-4d08-a8f3-d6cf3b8bc8e1
Most Windows Runtime attributes are projected mechanically, but a handful are not: some are
replaced by a .NET attribute that models the same concept ('[Deprecated]' -> '[Obsolete]',
'[Experimental]' -> '[Experimental]', '[Guid]' -> '[Guid]'), and some are consumed to shape the
generated code and never emitted at all. Those cases were only discoverable by reading the
projection writer and the WinMD generator, and they are exactly the ones a user is most likely to
be surprised by, in both directions.

'docs/attribute-projections.md' collects them into one table per direction: what a consumer sees
when Windows Runtime metadata is projected into C#, and what a component author has to write to
get a given Windows Runtime attribute into their '.winmd'. The projection table also records which
projection assemblies each result lands in, since almost everything there is
reference-projection-only.

The two asymmetries that are easiest to get wrong are called out explicitly: the metadata
attribute types that are not projected as types at all (because a .NET type takes their place),
and the fact that '[Obsolete]' on an authored component is not translated into '[Deprecated]', so
it is invisible to every other language projection.

Also adds the 'docs/diagnostics/cswinrt3005.md' page for the diagnostic id the projected
'[Experimental]' attribute reports, including how to opt into an experimental API and why it is an
error rather than a warning.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ed5549ac-fb33-4d08-a8f3-d6cf3b8bc8e1
@Sergio0694 Sergio0694 added documentation Improvements or additions to documentation tooling CsWinRT 3.0 labels Aug 1, 2026
@Sergio0694
Sergio0694 requested a review from manodasanW August 1, 2026 20:14
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

The projection writer tests feed the writer whichever Windows SDK is installed on the machine, so
an assertion that some API carries a given attribute is really an assertion about that SDK.
'Experimental_IsProjectedAsTheDotNetAttribute' did exactly that: it required at least one
'[Experimental]' API in 'Windows.Graphics.Capture' (the smallest namespace that had one locally),
and 'Windows.Graphics.Capture' was added to the generated namespaces just to reach it. That passes
against the SDK on my machine and fails on an agent with a different one, which is what CI hit: an
experimental API becoming stable, or not existing yet, is enough to break it.

The namespace list goes back to just 'Windows.Foundation', and the tests now assert the shape of
the projection instead, which is stable across SDKs: the Windows Runtime 'ExperimentalAttribute'
type is not projected and no application of it survives, while the metadata attribute types that
are not custom-mapped ('[Deprecated]', '[Overload]', '[Version]') still are. That second assertion
is what keeps the first honest, as both come from the same namespace and the same code path, so a
regression that stopped projecting 'Windows.Foundation.Metadata' entirely would otherwise satisfy
it for the wrong reason.

The dropped assertion is not lost, just moved to where it is deterministic: 'TestComponentCSharp'
declares an '[experimental]' runtime class, so the '#pragma warning disable CSWINRT3005' around its
use in 'UnitTest' only compiles while the writer emits exactly that diagnostic id.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ed5549ac-fb33-4d08-a8f3-d6cf3b8bc8e1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CsWinRT 3.0 documentation Improvements or additions to documentation tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant