microsoft / microsoft/typespec

C# generator exposes discriminator parameter on concrete model due to ApiCompatVersion

Open
#10,996 2 comments 1 reaction 0 assignees View on GitHub
bug emitter:client:csharp
Dominant language
Java
Stars
5.9k
Forks
394
Avg merge
1d 23h
Merged PRs (30d)
104

Description

## Summary

When `ApiCompatVersion` is set, the C# generator appears to preserve a discriminator base model as concrete to match the previous contract, but it still includes the required discriminator property as a public constructor parameter.

This exposes a discriminator value in the public API even though the discriminator property itself is internal.

## Repro context

Repository/package: `Azure.ResourceManager.Monitor`
Generator package: `@azure-typespec/http-client-csharp-mgmt` / `Microsoft.TypeSpec.Generator` `1.0.0-alpha.20260612.4`

Relevant TypeSpec:

```tsp
@discriminator("odata.type")
model MetricAlertCriteria {
...Record;

#suppress "@azure-tools/typespec-azure-core/casing-style" "FIXME: Update justification, follow aka.ms/tsp/conversion-fix for details"
`odata.type`: Odatatype;
}

@discriminator("criterionType")
model MultiMetricCriteria {
...Record;

criterionType: CriterionType;
name: string;
metricName: string;
metricNamespace?: string;
timeAggregation: AggregationTypeEnum;
@identifiers(#["name"])
dimensions?: MetricDimension[];
skipMetricValidation?: boolean;
}
```

## Actual behavior with ApiCompatVersion set

The generated public API has concrete classes with public constructors that expose discriminator parameters:

```csharp
public partial class MetricAlertCriteria
{
public MetricAlertCriteria(Odatatype odataType) { ... }
internal Odatatype OdataType { get; set; }
}

public partial class MultiMetricCriteria
{
public MultiMetricCriteria(
CriterionType criterionType,
string name,
string metricName,
MetricCriteriaTimeAggregationType timeAggregation) { ... }

internal CriterionType CriterionType { get; set; }
}
```

The discriminator property is correctly not public, but its value is still required as a public constructor parameter.

## Expected behavior

Discriminator values should not appear in public model constructors. If the generator makes the discriminator base concrete for back-compat, it should still avoid exposing discriminator parameters in public constructors, likely by restoring/keeping the previous public constructor shape and handling discriminator defaults internally.

## Evidence this is tied to ApiCompat/back-compat behavior

As an experiment, I temporarily removed `ApiCompatVersion` from `sdk/monitor/Azure.ResourceManager.Monitor/src/Azure.ResourceManager.Monitor.csproj` and regenerated.

Without `ApiCompatVersion`, both models became abstract and the discriminator constructors became `private protected`, which matches the normal discriminator-base pattern:

```csharp
public abstract partial class MetricAlertCriteria
{
private protected MetricAlertCriteria(Odatatype odataType) { ... }
internal Odatatype OdataType { get; set; }
}

public abstract partial class MultiMetricCriteria
{
private protected MultiMetricCriteria(
CriterionType criterionType,
string name,
string metricName,
MetricCriteriaTimeAggregationType timeAggregation) { ... }

internal CriterionType CriterionType { get; set; }
}
```

This suggests the back-compat feature reads the previous contract, sees the model was non-abstract, preserves concreteness, and unintentionally exposes required discriminator parameters in the public constructor.

## Related normal case

The existing generator test/project for a normal discriminator base (`Plant`) generates an abstract base model:

```csharp
public abstract partial class Plant
{
private protected Plant(string species, string id, int height) { ... }
internal string Species { get; set; }
}
```

That does not expose the discriminator in public API because the base model remains abstract. The issue appears specific to concrete discriminator bases produced/preserved for back compatibility.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.