From 509d7c411ceee3d72680533fc05d8fe89bf7a05b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:58:45 +0000 Subject: [PATCH 1/3] Initial plan From a032938936a520f2293f471ceacd33ce8458c362 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:16:13 +0000 Subject: [PATCH 2/3] fix(http-client-csharp): preserve error response names and isolate generic keep keys Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ClientBodyDependencyReferenceMapTests.cs | 57 ++++++++++++++++++- .../src/CodeModelGenerator.cs | 2 +- .../src/Providers/ModelProvider.cs | 3 +- ...iderReferenceMapAnalyzer.BodyReferences.cs | 2 +- .../ModelProviders/ModelProviderTests.cs | 31 ++++++++-- .../ProviderReferenceMapAnalyzerTests.cs | 28 +++++++++ 6 files changed, 114 insertions(+), 9 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ReferenceMap/ClientBodyDependencyReferenceMapTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ReferenceMap/ClientBodyDependencyReferenceMapTests.cs index 2eac575dd1f..1cb6bf36d7c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ReferenceMap/ClientBodyDependencyReferenceMapTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ReferenceMap/ClientBodyDependencyReferenceMapTests.cs @@ -7,9 +7,12 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.TypeSpec.Generator.ClientModel.Providers; +using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Statements; using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; @@ -73,6 +76,56 @@ public async Task OperationResponseBodyModelRemainsPublicAsRootOutputModel() await GenerateAndAssertPublicModels([responseModel], [client], ["ResponseBody"]); } + [TestCase("ErrorResponse", true, false)] + [TestCase("ErrorResponse", true, true)] + [TestCase("ServiceErrorResponse", true, false)] + [TestCase("ServiceErrorResponse", true, true)] + [TestCase("ErrorResult", false, false)] + [TestCase("ErrorResult", false, true)] + public async Task ErrorResultHelperDoesNotKeepServiceModel(string modelName, bool isError, bool referenced) + { + var model = InputFactory.Model(modelName, @namespace: "Sample", access: null!, + usage: InputModelTypeUsage.Json | (isError ? InputModelTypeUsage.Error : InputModelTypeUsage.None)); + var responseModel = InputFactory.Model("ResponseBody", + properties: referenced ? [InputFactory.Property("Details", model)] : []); + var operation = InputFactory.Operation("Get", responses: [InputFactory.OperationResponse(bodytype: responseModel)]); + var method = InputFactory.BasicServiceMethod("Get", operation, + response: InputFactory.ServiceMethodResponse(responseModel, [])); + var client = InputFactory.Client("TestClient", methods: [method]); + + await GenerateAndAssertFiles( + enums: [], + models: [responseModel, model], + clients: [client], + customFiles: [], + expectedFiles: [Path.Combine("src", "Generated", "Internal", "ErrorResult.cs")], + publicModelNames: referenced ? ["ResponseBody", modelName] : ["ResponseBody"], + assertProviders: (session, providers) => + { + var modelProvider = CodeModelGenerator.Instance.TypeFactory.CreateModel(model)!; + Assert.AreEqual(modelName, modelProvider.Name); + Assert.AreEqual(referenced, session.ShouldWriteProvider(modelProvider)); + foreach (var serialization in modelProvider.SerializationProviders) + { + Assert.AreEqual(referenced, session.ShouldWriteProvider(serialization)); + } + + var helper = providers.OfType().Single(); + Assert.AreEqual(modelProvider.Type.Namespace, helper.Type.Namespace); + Assert.AreEqual(1, helper.Type.Arguments.Count); + Assert.IsTrue(helper.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Internal)); + Assert.IsFalse(helper.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)); + + var factory = providers.OfType().Single(); + Assert.AreEqual(referenced, factory.Methods.Any(m => m.Signature.ReturnType?.Equals(modelProvider.Type) == true)); + + var context = providers.OfType().Single(); + var buildableTypes = context.GetAttributesForWrite().OfType() + .SelectMany(a => a.Arguments).OfType().Select(e => e.Type); + Assert.AreEqual(referenced, buildableTypes.Contains(modelProvider.Type)); + }); + } + [Test] public async Task InternalModelReferencedByPublicModelPropertyIsPublicized() { @@ -834,7 +887,8 @@ private static async Task GenerateAndAssertFiles( string[] internalModelNames = null!, string[] internalClientNames = null!, string packageName = "Sample", - Action? configureGenerator = null) + Action? configureGenerator = null, + Action>? assertProviders = null) { publicModelNames ??= []; internalModelNames ??= []; @@ -930,6 +984,7 @@ await MockHelpers.LoadMockGeneratorAsync( { AssertProviderWritten(session, allProviders, unexpectedFile, expected: false); } + assertProviders?.Invoke(session, providers); } private static IEnumerable EnumerateAllProviders(IEnumerable providers) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs index 5f668649e81..90b9ba1748a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs @@ -229,7 +229,7 @@ private static HashSet MaterializeKeepSet(KeptTypesInfo info) var result = new HashSet(info.TypeNames); foreach (var provider in info.TypeProviders) { - result.Add(provider.Type.FullyQualifiedName); + result.Add(ProviderReferenceMapAnalyzer.GetProviderTypeName(provider.Type)); } return result; } 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 f9c1c0eee6e..9530a84fbe4 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 @@ -321,7 +321,8 @@ protected override string BuildName() private protected override string NormalizeTypeName(string name) { var normalizedName = base.NormalizeTypeName(name); - if (!normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) + if (_inputModel.Usage.HasFlag(InputModelTypeUsage.Error) || + !normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) { return normalizedName; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/ReferenceMap/ProviderReferenceMapAnalyzer.BodyReferences.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/ReferenceMap/ProviderReferenceMapAnalyzer.BodyReferences.cs index 7e4e88f1ade..cc4a69d80da 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/ReferenceMap/ProviderReferenceMapAnalyzer.BodyReferences.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/ReferenceMap/ProviderReferenceMapAnalyzer.BodyReferences.cs @@ -598,7 +598,7 @@ private static string GetSimpleName(string fullyQualifiedName) return lastDot < 0 ? null : fullyQualifiedName.Substring(0, lastDot); } - private static string GetProviderTypeName(CSharpType type) + internal static string GetProviderTypeName(CSharpType type) { var name = type.Arguments.Count > 0 && !type.Name.Contains('`', StringComparison.Ordinal) ? $"{type.Name}`{type.Arguments.Count}" 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 4ae8e1324f0..aa1f0e6cd1f 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 @@ -154,6 +154,22 @@ public void TestBuildName_ResponseSuffix(string inputName, bool isExactName, str Assert.AreEqual("ServiceResponse", model.Properties[0].Name); } + [TestCase("ErrorResponse", false, "ErrorResponse")] + [TestCase("ServiceErrorResponse", false, "ServiceErrorResponse")] + [TestCase("IpResponse", false, "IPResponse")] + [TestCase("IpResponse", true, "IpResponse")] + public void TestBuildName_ErrorResponseSuffix(string inputName, bool isExactName, string expectedName) + { + var inputModel = InputFactory.Model( + inputName, + usage: InputModelTypeUsage.Error | InputModelTypeUsage.Output | InputModelTypeUsage.Json, + isExactName: isExactName); + var model = new ModelProvider(inputModel); + + Assert.AreEqual(expectedName, model.Name); + Assert.AreEqual($"{expectedName}.cs", Path.GetFileName(model.RelativeFilePath)); + } + [TestCase("WidgetResponse", "WidgetResponse", false, false)] [TestCase("WidgetResponse", "WidgetResponse", true, false)] [TestCase("WidgetResponse", "WidgetResponse", false, true)] @@ -188,12 +204,17 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.IsNotNull(lastContract ? model.LastContractView : model.CustomCodeView); } - [TestCase("WidgetResponse", "CustomizedWidget")] - [TestCase("IpResponse", "CustomizedIP")] - [TestCase("GadgetResponse", "CustomizedGadget")] - public async Task TestBuildName_ResponseSuffixPreservesCustomName(string inputName, string expectedName) + [TestCase("WidgetResponse", "CustomizedWidget", false)] + [TestCase("WidgetResponse", "CustomizedWidget", true)] + [TestCase("IpResponse", "CustomizedIP", false)] + [TestCase("IpResponse", "CustomizedIP", true)] + [TestCase("GadgetResponse", "CustomizedGadget", false)] + public async Task TestBuildName_ResponseSuffixPreservesCustomName( + string inputName, string expectedName, bool isError) { - var inputModel = InputFactory.Model(inputName); + var inputModel = InputFactory.Model(inputName, + usage: InputModelTypeUsage.Output | InputModelTypeUsage.Json | + (isError ? InputModelTypeUsage.Error : InputModelTypeUsage.None)); await MockHelpers.LoadMockGeneratorAsync( inputModelTypes: [inputModel], compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ReferenceMap/ProviderReferenceMapAnalyzerTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ReferenceMap/ProviderReferenceMapAnalyzerTests.cs index 2b9321d4a3f..7d8fcba40c4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ReferenceMap/ProviderReferenceMapAnalyzerTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ReferenceMap/ProviderReferenceMapAnalyzerTests.cs @@ -64,6 +64,34 @@ public void NonRootKeptTypesAreWrittenWithoutRootingOtherTypes() Assert.IsFalse(ProviderReferenceMapAnalyzer.ShouldWriteProvider(unusedModel)); } + [Test] + public void KeptProvidersDistinguishGenericArity( + [Values(false, true)] bool isRoot, + [Values(0, 1, 2)] int keptArity) + { + var argument = CreateNamedType("T", string.Empty); + TypeProvider[] providers = + [ + new TestTypeProvider("ErrorResult", TypeSignatureModifiers.Public, ns: "Sample"), + new GenericTestTypeProvider("ErrorResult", TypeSignatureModifiers.Internal, "Sample", argument), + new GenericTestTypeProvider("ErrorResult", TypeSignatureModifiers.Internal, "Sample", argument, CreateNamedType("U", string.Empty)) + ]; + MockHelpers.LoadMockGenerator(createOutputLibrary: () => new TestOutputLibrary(providers)); + CodeModelGenerator.Instance.AddTypeToKeep(providers[keptArity], isRoot); + + using var session = ProviderReferenceMapAnalyzer.PrepareForGeneration(providers); + + for (var i = 0; i < providers.Length; i++) + { + Assert.AreEqual(i == keptArity, session.ShouldWriteProvider(providers[i]), $"Arity {i}"); + } + if (!isRoot && keptArity > 0) + { + Assert.IsTrue(providers[keptArity].DeclarationModifiers.HasFlag(TypeSignatureModifiers.Internal)); + Assert.IsFalse(providers[keptArity].DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)); + } + } + [Test] public void ProviderNamedClientProviderIsNotTreatedAsClientWithoutCapability() { From 7beb8636e1390578d8b763d92938b1df0760a1f6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:23:33 +0000 Subject: [PATCH 3/3] test(http-client-csharp): verify nested namespace keep identities Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ProviderReferenceMapAnalyzerTests.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ReferenceMap/ProviderReferenceMapAnalyzerTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ReferenceMap/ProviderReferenceMapAnalyzerTests.cs index 7d8fcba40c4..7393e4cc293 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ReferenceMap/ProviderReferenceMapAnalyzerTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/ReferenceMap/ProviderReferenceMapAnalyzerTests.cs @@ -67,18 +67,31 @@ public void NonRootKeptTypesAreWrittenWithoutRootingOtherTypes() [Test] public void KeptProvidersDistinguishGenericArity( [Values(false, true)] bool isRoot, - [Values(0, 1, 2)] int keptArity) + [Values(0, 1, 2)] int keptArity, + [Values("Sample", "Sample.Nested.Models")] string ns) { var argument = CreateNamedType("T", string.Empty); TypeProvider[] providers = [ - new TestTypeProvider("ErrorResult", TypeSignatureModifiers.Public, ns: "Sample"), - new GenericTestTypeProvider("ErrorResult", TypeSignatureModifiers.Internal, "Sample", argument), - new GenericTestTypeProvider("ErrorResult", TypeSignatureModifiers.Internal, "Sample", argument, CreateNamedType("U", string.Empty)) + new TestTypeProvider("ErrorResult", TypeSignatureModifiers.Public, ns: ns), + new GenericTestTypeProvider("ErrorResult", TypeSignatureModifiers.Internal, ns, argument), + new GenericTestTypeProvider("ErrorResult", TypeSignatureModifiers.Internal, ns, argument, CreateNamedType("U", string.Empty)) ]; MockHelpers.LoadMockGenerator(createOutputLibrary: () => new TestOutputLibrary(providers)); CodeModelGenerator.Instance.AddTypeToKeep(providers[keptArity], isRoot); + var expectedName = keptArity == 0 ? $"{ns}.ErrorResult" : $"{ns}.ErrorResult`{keptArity}"; + var keepSet = isRoot ? CodeModelGenerator.Instance.AdditionalRootTypes : CodeModelGenerator.Instance.NonRootTypes; + Assert.That(keepSet, Contains.Item(expectedName)); + if (keptArity == 0) + { + Assert.AreEqual(providers[keptArity].Type.FullyQualifiedName, expectedName); + } + else + { + Assert.That(keepSet, Does.Not.Contain(providers[keptArity].Type.FullyQualifiedName)); + } + using var session = ProviderReferenceMapAnalyzer.PrepareForGeneration(providers); for (var i = 0; i < providers.Length; i++)