From 9972047d16a8b4dca74d63778001dd0ba837d04e Mon Sep 17 00:00:00 2001 From: Dapeng Zhang Date: Tue, 23 Jun 2026 15:29:10 +0800 Subject: [PATCH] fix(http-client-csharp): hide discriminator params in compat constructors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 13 ++++++ .../ModelProviders/ModelProviderTests.cs | 41 +++++++++++++++++++ .../BaseModel.cs | 12 ++++++ 3 files changed, 66 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConcreteDiscriminatorBaseDoesNotExposeDiscriminatorConstructorParameter/BaseModel.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs index ca60cb274f9..2ce059b5a22 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs @@ -966,6 +966,11 @@ private IEnumerable GetAllBaseFieldsForConstructorInitialization( // Build constructor parameters first so we can use them for initializer foreach (var property in CanonicalView.Properties) { + if (ShouldOmitDiscriminatorParameterFromPublicConstructor(property, isInitializationConstructor)) + { + continue; + } + AddInitializationParameterForCtor(constructorParameters, Type.IsStruct, isInitializationConstructor, property); } @@ -1029,6 +1034,14 @@ p.Property is null return (constructorParameters, constructorInitializer); } + private bool ShouldOmitDiscriminatorParameterFromPublicConstructor(PropertyProvider property, bool isInitializationConstructor) + => isInitializationConstructor + && property.IsDiscriminator + && _inputModel.DiscriminatorProperty is not null + && _inputModel.DiscriminatorValue is null + && _inputModel.Usage.HasFlag(InputModelTypeUsage.Input) + && !DeclarationModifiers.HasFlag(TypeSignatureModifiers.Abstract); + private bool HasBaseModelProviderCycle() { // FullConstructor reads the base constructor signature. If the custom base chain loops back diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs index 128fbab1c94..fc08d61c6ea 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs @@ -1425,6 +1425,47 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.IsTrue(modelProvider.Constructors.Any(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public))); } + [Test] + public async Task BackCompat_ConcreteDiscriminatorBaseDoesNotExposeDiscriminatorConstructorParameter() + { + var discriminatorEnum = InputFactory.StringEnum("kindEnum", [("One", "one"), ("Two", "two")]); + var derivedInputModel = InputFactory.Model( + "DerivedModel", + discriminatedKind: "one", + properties: + [ + InputFactory.Property("kind", InputFactory.EnumMember.String("One", "one", discriminatorEnum), isRequired: true, isDiscriminator: true), + InputFactory.Property("derivedProp", InputPrimitiveType.Int32, isRequired: true) + ]); + var inputModel = InputFactory.Model( + "BaseModel", + properties: + [ + InputFactory.Property("kind", discriminatorEnum, isRequired: true, isDiscriminator: true), + InputFactory.Property("baseProp", InputPrimitiveType.String, isRequired: true) + ], + discriminatedModels: new Dictionary() { { "one", derivedInputModel } }); + + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [inputModel], + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelProvider = CodeModelGenerator.Instance.OutputLibrary.TypeProviders.SingleOrDefault(t => t.Name == "BaseModel") as ModelProvider; + + Assert.IsNotNull(modelProvider); + Assert.IsFalse(modelProvider!.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Abstract)); + + modelProvider.ProcessTypeForBackCompatibility(); + + var publicConstructors = modelProvider.Constructors + .Where(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)) + .ToArray(); + Assert.AreEqual(1, publicConstructors.Length); + Assert.AreEqual(1, publicConstructors[0].Signature.Parameters.Count); + Assert.AreEqual("baseProp", publicConstructors[0].Signature.Parameters[0].Name); + Assert.IsFalse(publicConstructors[0].Signature.Parameters.Any(p => p.Property?.IsDiscriminator == true)); + } + [Test] public async Task BackCompat_NonAbstractTypeIsRespected_NamespaceChangedInVisitor() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConcreteDiscriminatorBaseDoesNotExposeDiscriminatorConstructorParameter/BaseModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConcreteDiscriminatorBaseDoesNotExposeDiscriminatorConstructorParameter/BaseModel.cs new file mode 100644 index 00000000000..b59ff33e62f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConcreteDiscriminatorBaseDoesNotExposeDiscriminatorConstructorParameter/BaseModel.cs @@ -0,0 +1,12 @@ +namespace Sample.Models +{ + public partial class BaseModel + { + public BaseModel(string baseProp) + { + BaseProp = baseProp; + } + + public string BaseProp { get; set; } + } +}