Skip to content

Conversation

@radhgupta
Copy link
Member

@radhgupta radhgupta commented Nov 13, 2025

This pull request introduces a new dual constructor pattern for intermediate models in discriminated union hierarchies, ensuring correct instantiation and inheritance when models share discriminator property names with their base models. It adds logic to detect when this pattern is needed, generates the appropriate constructors, and updates constructor initialization to support multi-layer discriminator scenarios. Several new tests are included to validate these changes.

Constructor Pattern Enhancements

  • Added a new ConstructorType enum to define different constructor generation strategies, supporting public, private protected, and internal constructors for various use cases.
  • Implemented logic in ModelProvider to detect when a model should use the dual constructor pattern (i.e., when it shares a discriminator property name with its base and has derived models).
  • Added methods to generate three constructors for intermediate discriminated models: a public constructor (for external use), a private protected constructor (for inheritance), and an internal constructor (for serialization).

Constructor Initialization Logic

  • Updated constructor initialization to correctly call the appropriate base constructor, including passing discriminator values when required for intermediate models using the dual constructor pattern.

Testing Multi-Layer Discriminator Hierarchies

  • Added comprehensive tests to cover multi-layer discriminator scenarios, including cases with and without discriminator properties at intermediate layers, and verifying correct constructor argument initialization.

Minor Cleanup

  • Removed an unused discriminatedKind argument in a test setup for clarity.

Addresses: Azure/azure-sdk-for-net#51958

@microsoft-github-policy-service microsoft-github-policy-service bot added the emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp label Nov 13, 2025
@github-actions
Copy link
Contributor

No changes needing a change description found.

@radhgupta radhgupta marked this pull request as ready for review November 14, 2025 18:18
|| (p.Property.IsDiscriminator && !overriddenProperties.Contains(p.Property) && (!isPrimaryConstructor || includeDiscriminatorParameter))
|| (!p.Property.IsDiscriminator && !overriddenProperties.Contains(p.Property))));

if (includeDiscriminatorParameter && _inputModel.DiscriminatorProperty != null)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the filtering that we need to adjust happens here - https://github.com/microsoft/typespec/blob/main/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs#L586-L594. That filtering is designed for only one layer of inheritance for a single discriminator. Now that we support N layers for a single discriminator, we need to adjust the condition. This will allow us to not have special handling for creating the discriminator param, and the name/description will come directly from the property.

Copy link
Member Author

@radhgupta radhgupta Nov 17, 2025

Choose a reason for hiding this comment

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

Oh, so do you mean that rather than creating the discriminator parameter, I simply remove the if (isDirectBase) from GetAllBasePropertiesForConstructorInitialization this way I dont have to create the discriminator and do not need to create includeDiscriminatorParameter flag in BuildConstructorParameters?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we still need some logic to do what the isDirectBase is currently doing. The purpose of that was so that we don't add base properties when there are nested discriminators from an ancestor further than the direct base. So I think you are right we can remove the isDirectBase check but we will need to add a new check that the discriminator property from the base model matches the current model's discriminator property.

public class ModelProvider : TypeProvider
{
private const string AdditionalBinaryDataPropsFieldDescription = "Keeps track of any properties unknown to the library.";
private const string DiscriminatorParameterName = "discriminatorValue";
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think we don't need these anymore

/// This is needed when the model shares the same discriminator property name as its base model
/// AND has derived models, indicating it's an intermediate type in a discriminated union hierarchy.
/// </summary>
private bool ShouldHaveDualConstructorPattern()
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we compute this and store in a field called _isMultiLevelDiscriminator?

if (isDirectBase)
// Only include discriminator properties from the direct base that match the current model's discriminator property name.
// This handles N layers of inheritance while ensuring we don't include multiple discriminator parameters
if (isDirectBase && _inputModel.DiscriminatorProperty != null &&
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be if (isDirectBase || _isMultiLevelDiscriminator)

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually we probably need to add a parameter in this method (and check that instead) since we only want to do this for one of the constructors for multilevel discriminator models. Something like:

if (isDirectBase || includeHierarchyDiscriminator)

var constructors = new List<ConstructorProvider> { publicConstructor };

// If this model needs dual constructor pattern, add the private protected constructor
if (ShouldHaveDualConstructorPattern())
Copy link
Contributor

Choose a reason for hiding this comment

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

if it is a multiLevelDiscriminator I think we can just add the additional constructor and let the other constructor creation happen as is

@JoshLove-msft
Copy link
Contributor

Looks like the CI is failing

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.

2 participants