Skip to content

Emit experimental diagnostics in C# clients - #11685

Open
JoshLove-msft wants to merge 2 commits into
microsoft:mainfrom
JoshLove-msft:josh/csharp-experimental-diagnostic-id
Open

Emit experimental diagnostics in C# clients#11685
JoshLove-msft wants to merge 2 commits into
microsoft:mainfrom
JoshLove-msft:josh/csharp-experimental-diagnostic-id

Conversation

@JoshLove-msft

@JoshLove-msft JoshLove-msft commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • preserve TypeSpec.HttpClient.@experimental diagnostic metadata in the C# input model
  • emit [Experimental("C")] on generated protocol and convenience methods
  • emit scoped warning suppressions for each dependsOn diagnostic in generated method and request bodies
  • keep graduation explicit; the attribute remains until the decorator is removed

Companion to #11684, which adds diagnosticId and dependsOn to the shared decorator contract.

Validation

  • C# emitter build
  • focused emitter, input, and generator tests
  • Cop static-analysis checks
  • generated-library regeneration

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: f0e7f0ac-c4b6-47e3-a4fc-430ff8f883c6
@pkg-pr-new

pkg-pr-new Bot commented Aug 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-csharp@11685

commit: 06639a1

@github-actions

Copy link
Copy Markdown
Contributor

No changes needing a change description found.

};
}

function isEmitterScopeApplicable(emitterScope: string | undefined): boolean {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this already handled by TCGC?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TCGC does filter scope, but it currently checks only a top-level decoratorInfo.arguments["scope"]. @experimental receives ClientDecoratorOptions through its options parameter, so this value arrives as decoratorInfo.arguments.options.emitterScope and is not filtered by TCGC. I kept the local check, added a comment explaining the distinction, and retained coverage verifying metadata scoped to another emitter is ignored.

--generated by Copilot

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: f0e7f0ac-c4b6-47e3-a4fc-430ff8f883c6
Copilot AI lite review requested due to automatic review settings August 15, 2026 05:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR propagates TypeSpec.HttpClient.@experimental metadata through the C# emitter input model and uses it during C# client generation to (a) emit ExperimentalAttribute on generated API methods and (b) add scoped warning suppressions for diagnostics listed in dependsOn.

Changes:

  • Extend the emitter/input-model contract to carry experimental: { diagnosticId, dependsOn } for operations, including JSON deserialization support and unit tests.
  • Emit [Experimental("...")] on generated protocol and convenience methods when diagnosticId is present, and add #pragma warning disable/restore for each dependsOn diagnostic on generated methods (including request creation methods).
  • Add emitter-side decorator extraction + tests to ensure @experimental metadata is captured with correct emitter scoping behavior.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/common/InputFactory.cs Adds optional experimental details when creating InputOperation test inputs.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/test/TypeSpecInputConverterTests.cs Adds a deserialization test validating experimental.diagnosticId and experimental.dependsOn.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/Serialization/InputOperationConverter.cs Deserializes the new experimental payload into InputOperation.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputOperation.cs Stores Experimental details on InputOperation and threads it through constructors.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputTypes/InputExperimentalDetails.cs Introduces a new input-model type to represent experimental diagnostic metadata.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmMethodProviderCollectionTests.cs Validates attribute emission and dependency suppressions for experimental operations.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ExperimentalApiHelpers.cs Centralizes building experimental attributes and dependency suppressions.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmMethodProviderCollection.cs Applies experimental attributes/suppressions to generated protocol and convenience methods.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/RestClientProvider.cs Applies dependency suppressions to generated create-request methods.
packages/http-client-csharp/emitter/test/Unit/experimental-decorator.test.ts Adds unit tests for extracting @experimental diagnostic metadata (including emitter scoping).
packages/http-client-csharp/emitter/src/type/input-operation.ts Extends the TS input operation type to include experimental.
packages/http-client-csharp/emitter/src/options.ts Ensures @experimental is captured via additionalDecorators.
packages/http-client-csharp/emitter/src/lib/operation-converter.ts Populates operation.experimental from decorators during conversion.
packages/http-client-csharp/emitter/src/lib/decorators.ts Adds getExperimentalDetails and emitter-scope filtering for @experimental metadata.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +63 to +74
const scopes = emitterScope
.split(",")
.map((scope) => scope.trim())
.filter((scope) => scope.length > 0);
const excludedScopes = scopes
.filter((scope) => scope.startsWith("!"))
.map((scope) => scope.slice(1));
if (excludedScopes.length > 0) {
return !excludedScopes.includes(csharpEmitterName);
}

return scopes.includes(csharpEmitterName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if tcgc already exports some util function we can use here instead ?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

crossLanguageDefinitionId: string;
decorators?: DecoratorInfo[];
namespace?: string;
experimental?: InputExperimentalDetails;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we intionally scoping this only for operations at the moment? I'm assuming we'll want to use this for other types in the future ?


namespace Microsoft.TypeSpec.Generator.Input
{
public sealed class InputExperimentalDetails

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need a serialization / converter type for this ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to update the tspd docs ?

Chidozie Ononiwu (chidozieononiwu) pushed a commit to chidozieononiwu/typespec that referenced this pull request Aug 17, 2026
## Motivation

`@experimental` currently records only that a TypeSpec declaration is
experimental. That is enough for emitters that need a boolean lifecycle
flag, but it does not describe how an emitter should identify the
experiment or whether the generated implementation relies on other
experimental features.

A generated API can be a distinct public experiment while being composed
from several lower-level experiments. For example, an operation tracked
as experiment `C` may internally use experimental features `A` and `B`.
Consumers should see `C`, while an emitter may need `A` and `B` to
generate warning suppressions around implementation code.

## Usage

```typespec
@experimental(#{
  emitterScope: "@typespec/http-client-csharp",
  diagnosticId: "C",
  dependsOn: #["A", "B"]
})
op bar(): void;
```

### Scenarios

- **Direct experiment:** specify only `diagnosticId` when an emitter
needs a stable identifier for the experimental API.
- **Composed experiment:** use `dependsOn` when the generated
implementation consumes other independently experimental features.
- **Emitter-specific diagnostics:** use `emitterScope` when identifiers
are meaningful only to a particular emitter or target language.
- **Lifecycle tooling:** emitters and tooling can query the complete
lifecycle details without parsing decorator syntax.
- **Explicit graduation:** dependencies becoming generally available
does not silently graduate the public API. The declaration remains
experimental until its decorator is explicitly removed.

## API changes

- Add optional `diagnosticId` and `dependsOn` fields to
`FeatureLifecycleOptions`.
- Add `getFeatureLifecycleDetails`, returning:

```ts
{
  stage: "Experimental";
  diagnosticId?: string;
  dependsOn: readonly string[];
}
```

- Preserve the existing `getFeatureLifecycle` API and its
`"Experimental" | undefined` behavior for compatibility.
- Apply existing emitter-scope filtering to all lifecycle details.

## Emitter behavior

The metadata is descriptive rather than prescriptive. Each emitter
decides how to represent it. The C# companion implementation in microsoft#11685
maps `diagnosticId` to `ExperimentalAttribute` and `dependsOn` to scoped
warning suppressions.

## Related work

- C# emitter implementation: microsoft#11685
- Design issue: microsoft#11690

## Validation

- `@typespec/http-client` build and full test suite
- Affected-package lint and formatting checks

Copilot-Session: f0e7f0ac-c4b6-47e3-a4fc-430ff8f883c6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants