Project the Windows Runtime [Experimental] attribute as the .NET one - #2503
Open
Sergio0694 wants to merge 4 commits into
Open
Conversation
'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
|
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
Sergio0694
force-pushed
the
user/sergiopedri/project-experimental-attribute
branch
from
August 1, 2026 22:07
038fbdb to
a741f58
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
Custom-map
Windows.Foundation.Metadata.ExperimentalAttributetoSystem.Diagnostics.CodeAnalysis.ExperimentalAttribute, with theCSWINRT3005diagnostic id, in both directions (projection and component authoring). Also addsdocs/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.ExperimentalAttributewas projected as an ordinary Windows Runtime attribute type, and the C# compiler happened to recognize it by name and reportCS8305for 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 treatmentIClosable,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-mapsWindows.Foundation.Metadata.ExperimentalAttributetoSystem.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 aWriteAttributehelper 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 suppressCSWINRT3005in their prelude, alongside theCS0612/CS0618they 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. UnlikeCS8305, 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-redundantCS8305NoWarn(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: translatesSystem.Diagnostics.CodeAnalysis.ExperimentalAttributeintoWindows.Foundation.Metadata.ExperimentalAttribute. The diagnostic id,UrlFormatandMessageare 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.WinMDGeneratorRunnergains the ability to read attributes back out of the generated.winmd(viaSystem.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 existingCS8305suppression toCSWINRT3005, and adds a test asserting the attribute does not survive into the implementation projection loaded at runtime.src/Tests/WinMDGeneratorTest/WinMDGeneratorTest.csproj: dropsPlatformfrom the generatorProjectReference'sUndefineProperties, matchingProjectionWriterTest. Without it, building with an explicit platform puts the tool underbin\<platform>\while the tests look for it underbin\, 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.