From 62f2a61178b4118c40304473fb1ca65e4fc14966 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 17 Sep 2026 17:34:09 +0000 Subject: [PATCH 01/68] Initial plan From d37edbaa33349d6647b6ab6b3d5488ddc87fe711 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 19:40:21 +0000 Subject: [PATCH 02/68] test(csharp): cover deterministic Response-to-Result model names Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../EnumProviders/EnumProviderTests.cs | 2 + .../ModelProviders/ModelProviderTests.cs | 116 ++++++++++++++++++ .../WidgetResult.cs | 9 ++ .../CustomizedModels.cs | 19 +++ .../ExistingModels.cs | 14 +++ 5 files changed, 160 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsCustomizationCollision/WidgetResult.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesCustomName/CustomizedModels.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesExistingName/ExistingModels.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderTests.cs index b319b9c71d3..f0f3339e5dc 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/EnumProviders/EnumProviderTests.cs @@ -97,6 +97,8 @@ public void BuildEnumType_ValidateStringBasedFixedEnum() [TestCase(false, "IPV4AddressIPV6", false, "IPV4AddressIPV6")] [TestCase(true, "OsloIpsumOsmosisDbz", false, "OsloIpsumOsmosisDbz")] [TestCase(false, "IpKind", true, "IpKind")] + [TestCase(false, "WidgetResponse", false, "WidgetResponse")] + [TestCase(true, "IpResponse", false, "IPResponse")] public void BuildEnumType_NormalizesTypeAcronymCasing( bool isExtensible, string inputName, 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 ed9345d2f60..01433f7cf40 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 @@ -130,6 +130,122 @@ public void TestBuildName_NormalizesAcronymCasing(string inputName, bool isExact Assert.AreEqual(expectedName, modelProvider.Name); } + [TestCase("WidgetResponse", false, "WidgetResult")] + [TestCase("Response", false, "Result")] + [TestCase("widget_response", false, "WidgetResult")] + [TestCase("IpResponse", false, "IPResult")] + [TestCase("WidgetResponseResponse", false, "WidgetResponseResult")] + [TestCase("WidgetResponse", true, "WidgetResponse")] + [TestCase("IpResponse", true, "IpResponse")] + [TestCase("WidgetResult", false, "WidgetResult")] + [TestCase("ResponseWidget", false, "ResponseWidget")] + [TestCase("WidgetResponses", false, "WidgetResponses")] + [TestCase("Widgetresponse", false, "Widgetresponse")] + public void TestBuildName_ResponseSuffix(string inputName, bool isExactName, string expectedName) + { + var inputModel = InputFactory.Model( + inputName, + isExactName: isExactName, + properties: [InputFactory.Property("serviceResponse", InputPrimitiveType.String)]); + var model = new ModelProvider(inputModel); + + Assert.AreEqual(expectedName, model.Name); + Assert.AreEqual($"{expectedName}.cs", Path.GetFileName(model.RelativeFilePath)); + Assert.AreEqual("ServiceResponse", model.Properties[0].Name); + } + + [TestCase("WidgetResponse", "WidgetResponse", false, false)] + [TestCase("WidgetResponse", "WidgetResponse", true, false)] + [TestCase("WidgetResponse", "WidgetResponse", false, true)] + [TestCase("WidgetResponse", "WidgetResponse", true, true)] + [TestCase("IpResponse", "IPResponse", false, false)] + [TestCase("IpResponse", "IPResponse", true, false)] + [TestCase("IpResponse", "IPResponse", false, true)] + [TestCase("IpResponse", "IPResponse", true, true)] + [TestCase("DbResponse", "DbResponse", false, false)] + [TestCase("DbResponse", "DbResponse", true, false)] + [TestCase("DbResponse", "DbResponse", false, true)] + [TestCase("DbResponse", "DbResponse", true, true)] + public async Task TestBuildName_ResponseSuffixPreservesExistingName( + string inputName, string expectedName, bool lastContract, bool updateNamespace) + { + var inputModel = InputFactory.Model( + inputName, @namespace: updateNamespace ? "Sample" : "Sample.Models"); + var compilation = await Helpers.GetCompilationFromDirectoryAsync(); + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [inputModel], + compilation: lastContract ? null : () => Task.FromResult(compilation), + lastContractCompilation: lastContract ? () => Task.FromResult(compilation) : null); + var model = CodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; + + if (updateNamespace) + { + Assert.That(model.Name, Does.EndWith("Result")); + model.Update(@namespace: "Sample.Models"); + } + + Assert.AreEqual(expectedName, model.Name); + 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) + { + var inputModel = InputFactory.Model(inputName); + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [inputModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var model = CodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; + + Assert.AreEqual(expectedName, model.Name); + Assert.IsNotNull(model.CustomCodeView); + } + + [TestCase(false, false)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(true, true)] + public void TestBuildName_ResponseSuffixAvoidsModelCollision(bool reverseOrder, bool differentNamespace) + { + var response = InputFactory.Model("WidgetResponse"); + var result = InputFactory.Model("WidgetResult", @namespace: differentNamespace ? "Other" : "Sample.Models"); + InputModelType[] models = reverseOrder ? [result, response] : [response, result]; + MockHelpers.LoadMockGenerator(inputModelTypes: models); + + var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); + + CollectionAssert.AreEquivalent(new[] { "WidgetResponse", "WidgetResult" }, providers.Select(p => p.Name)); + } + + [TestCase(false)] + [TestCase(true)] + public void TestBuildName_ResponseSuffixAvoidsEnumCollision(bool isExtensible) + { + var response = InputFactory.Model("IpResponse"); + var result = InputFactory.StringEnum("IpResult", [("Value", "value")], isExtensible: isExtensible); + MockHelpers.LoadMockGenerator(inputModelTypes: [response], inputEnumTypes: [result]); + + Assert.AreEqual("IPResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + Assert.AreEqual("IPResult", CodeModelGenerator.Instance.TypeFactory.CreateEnum(result)!.Name); + } + + [TestCase("OtherModel")] + [TestCase("WidgetResult")] + public async Task TestBuildName_ResponseSuffixAvoidsCustomizationCollision(string otherName) + { + var response = InputFactory.Model("WidgetResponse"); + var other = InputFactory.Model(otherName); + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [response, other], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + Assert.AreEqual("WidgetResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(other)!.Name); + } + [Test] public async Task TestBuildName_BackCompatTakesPrecedenceOverAcronymNormalization() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsCustomizationCollision/WidgetResult.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsCustomizationCollision/WidgetResult.cs new file mode 100644 index 00000000000..db82a5a75a5 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsCustomizationCollision/WidgetResult.cs @@ -0,0 +1,9 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("OtherModel")] + public partial class WidgetResult + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesCustomName/CustomizedModels.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesCustomName/CustomizedModels.cs new file mode 100644 index 00000000000..9f06d5460bb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesCustomName/CustomizedModels.cs @@ -0,0 +1,19 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("WidgetResponse")] + public partial class CustomizedWidget + { + } + + [CodeGenType("IPResponse")] + public partial class CustomizedIP + { + } + + [CodeGenType("GadgetResult")] + public partial class CustomizedGadget + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesExistingName/ExistingModels.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesExistingName/ExistingModels.cs new file mode 100644 index 00000000000..fc09d2295c4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesExistingName/ExistingModels.cs @@ -0,0 +1,14 @@ +namespace Sample.Models +{ + public partial class WidgetResponse + { + } + + public partial class IPResponse + { + } + + public partial class DbResponse + { + } +} From 4af1e99de679962da08b9c2bbf0170b681711922 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 19:53:26 +0000 Subject: [PATCH 03/68] feat(csharp): normalize Response model suffixes to Result Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ClientBodyDependencyReferenceMapTests.cs | 10 +- .../src/Providers/TypeProvider.cs | 106 +++++++++++++++++- .../ModelProviders/ModelProviderTests.cs | 29 +++-- ...hContinuationTokenAsyncCollectionResult.cs | 4 +- ...ntinuationTokenAsyncCollectionResultOfT.cs | 6 +- ...etWithContinuationTokenCollectionResult.cs | 4 +- ...ithContinuationTokenCollectionResultOfT.cs | 6 +- ...nHeaderResponseAsyncCollectionResultOfT.cs | 2 +- ...nTokenHeaderResponseCollectionResultOfT.cs | 2 +- ...entGetWithNextLinkAsyncCollectionResult.cs | 4 +- ...GetWithNextLinkAsyncCollectionResultOfT.cs | 6 +- ...ecClientGetWithNextLinkCollectionResult.cs | 4 +- ...lientGetWithNextLinkCollectionResultOfT.cs | 6 +- ...WithStringNextLinkAsyncCollectionResult.cs | 4 +- ...hStringNextLinkAsyncCollectionResultOfT.cs | 6 +- ...ntGetWithStringNextLinkCollectionResult.cs | 4 +- ...etWithStringNextLinkCollectionResultOfT.cs | 6 +- .../Sample-TypeSpec/src/Generated/Metrics.cs | 8 +- ....cs => GetNotebookResult.Serialization.cs} | 52 ++++----- ...tebookResponse.cs => GetNotebookResult.cs} | 12 +- ...> GetWidgetMetricsResult.Serialization.cs} | 52 ++++----- ...sResponse.cs => GetWidgetMetricsResult.cs} | 12 +- ...okenHeaderResponseResult.Serialization.cs} | 52 ++++----- ...hContinuationTokenHeaderResponseResult.cs} | 12 +- ...hContinuationTokenResult.Serialization.cs} | 52 ++++----- ....cs => ListWithContinuationTokenResult.cs} | 12 +- ...> ListWithNextLinkResult.Serialization.cs} | 52 ++++----- ...kResponse.cs => ListWithNextLinkResult.cs} | 12 +- ...WithStringNextLinkResult.Serialization.cs} | 52 ++++----- ...nse.cs => ListWithStringNextLinkResult.cs} | 12 +- ...urnsAnonymousModelResult.Serialization.cs} | 48 ++++---- ...onse.cs => ReturnsAnonymousModelResult.cs} | 12 +- .../Generated/Models/SampleTypeSpecContext.cs | 14 +-- .../src/Generated/Notebooks.cs | 8 +- .../src/Generated/SampleTypeSpecClient.cs | 8 +- .../Generated/SampleTypeSpecModelFactory.cs | 24 ++-- .../Models/GetResponse.Serialization.cs | 36 ------ .../Models/GetResult.Serialization.cs | 36 ++++++ .../Models/{GetResponse.cs => GetResult.cs} | 2 +- .../src/Generated/Models/_TypeUnionContext.cs | 2 +- .../type/union/src/Generated/StringsOnly.cs | 4 +- .../src/Generated/TypeUnionModelFactory.cs | 2 +- 42 files changed, 453 insertions(+), 344 deletions(-) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{GetNotebookResponse.Serialization.cs => GetNotebookResult.Serialization.cs} (68%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{GetNotebookResponse.cs => GetNotebookResult.cs} (77%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{GetWidgetMetricsResponse.Serialization.cs => GetWidgetMetricsResult.Serialization.cs} (67%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{GetWidgetMetricsResponse.cs => GetWidgetMetricsResult.cs} (76%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{ListWithContinuationTokenHeaderResponseResponse.Serialization.cs => ListWithContinuationTokenHeaderResponseResult.Serialization.cs} (72%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{ListWithContinuationTokenHeaderResponseResponse.cs => ListWithContinuationTokenHeaderResponseResult.cs} (74%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{ListWithContinuationTokenResponse.Serialization.cs => ListWithContinuationTokenResult.Serialization.cs} (69%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{ListWithContinuationTokenResponse.cs => ListWithContinuationTokenResult.cs} (72%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{ListWithNextLinkResponse.Serialization.cs => ListWithNextLinkResult.Serialization.cs} (69%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{ListWithNextLinkResponse.cs => ListWithNextLinkResult.cs} (74%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{ListWithStringNextLinkResponse.Serialization.cs => ListWithStringNextLinkResult.Serialization.cs} (69%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{ListWithStringNextLinkResponse.cs => ListWithStringNextLinkResult.cs} (73%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{ReturnsAnonymousModelResponse.Serialization.cs => ReturnsAnonymousModelResult.Serialization.cs} (65%) rename packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/{ReturnsAnonymousModelResponse.cs => ReturnsAnonymousModelResult.cs} (67%) delete mode 100644 packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResponse.Serialization.cs create mode 100644 packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResult.Serialization.cs rename packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/{GetResponse.cs => GetResult.cs} (77%) 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 8b3d23beffb..2eac575dd1f 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 @@ -177,7 +177,7 @@ await GenerateAndAssertFiles( clients: [client], customFiles: [], expectedFiles: [], - publicModelNames: ["MetadataOnlyResponse"]); + publicModelNames: ["MetadataOnlyResult"]); } [Test] @@ -227,13 +227,13 @@ await GenerateAndAssertFiles( clients: [client], customFiles: [], expectedFiles: [ - Path.Combine("src", "Generated", "Models", "MetadataOnlyResponse.cs"), - Path.Combine("src", "Generated", "Models", "MetadataOnlyResponse.Serialization.cs") + Path.Combine("src", "Generated", "Models", "MetadataOnlyResult.cs"), + Path.Combine("src", "Generated", "Models", "MetadataOnlyResult.Serialization.cs") ], - internalModelNames: ["MetadataOnlyResponse"], + internalModelNames: ["MetadataOnlyResult"], configureGenerator: () => { - var provider = CodeModelGenerator.Instance.OutputLibrary.TypeProviders.Single(provider => provider.Name == "MetadataOnlyResponse"); + var provider = CodeModelGenerator.Instance.OutputLibrary.TypeProviders.Single(provider => provider.Name == "MetadataOnlyResult"); CodeModelGenerator.Instance.AddTypeToKeep(provider); }); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs index 564e3bbd43f..4bf30ab77b2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs @@ -45,10 +45,36 @@ protected TypeProvider() : this(null) } private protected virtual TypeProvider? BuildCustomCodeView(string? generatedTypeName = null, string? generatedTypeNamespace = null) - => CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( - generatedTypeNamespace ?? BuildNamespace(), - generatedTypeName ?? BuildName(), + { + var typeNamespace = generatedTypeNamespace ?? BuildNamespace(); + var typeName = generatedTypeName ?? BuildName(); + var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + var customCodeView = sourceInputModel.FindForTypeInCurrentCompilation( + typeNamespace, + typeName, _declaringTypeName.Value); + if (customCodeView is not null || this is not ModelProvider || _inputType is null || _inputType.IsExactName) + { + return customCodeView; + } + + var originalName = _inputType.Name.ToIdentifierName(); + if (!originalName.EndsWith("Response", StringComparison.Ordinal) || + originalName == typeName || + typeName != NormalizeTypeName(originalName)) + { + return null; + } + + return sourceInputModel.FindForTypeInCurrentCompilation( + typeNamespace, + originalName, + _declaringTypeName.Value) ?? + sourceInputModel.FindForTypeInCurrentCompilation( + typeNamespace, + originalName.NormalizeCSharpAcronyms(), + _declaringTypeName.Value); + } private protected virtual TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) { @@ -64,7 +90,7 @@ protected TypeProvider() : this(null) } var originalName = _inputType.Name.ToIdentifierName(); - var normalizedOriginalName = originalName.NormalizeCSharpAcronyms(); + var normalizedOriginalName = NormalizeTypeName(originalName); if (normalizedOriginalName == originalName || typeName != normalizedOriginalName) { return null; @@ -73,7 +99,11 @@ protected TypeProvider() : this(null) return CodeModelGenerator.Instance.SourceInputModel.FindForTypeInLastContract( typeNamespace, originalName, - _declaringTypeName.Value); + _declaringTypeName.Value) ?? + CodeModelGenerator.Instance.SourceInputModel.FindForTypeInLastContract( + typeNamespace, + originalName.NormalizeCSharpAcronyms(), + _declaringTypeName.Value); } private static string? GetDeclaringTypeName(TypeProvider? declaringTypeProvider) @@ -748,7 +778,7 @@ protected string NormalizeTypeNameForNewContract(string name) return name; } - var normalizedName = name.NormalizeCSharpAcronyms(); + var normalizedName = NormalizeTypeName(name); if (normalizedName == name) { return name; @@ -761,6 +791,70 @@ protected string NormalizeTypeNameForNewContract(string name) return lastContractType is null ? normalizedName : name; } + private string NormalizeTypeName(string name) + { + var normalizedName = name.NormalizeCSharpAcronyms(); + const string responseSuffix = "Response"; + if (this is not ModelProvider || !normalizedName.EndsWith(responseSuffix, StringComparison.Ordinal)) + { + return normalizedName; + } + + var typeNamespace = BuildNamespace(); + var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + if (sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, normalizedName, _declaringTypeName.Value) is not null || + sourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName, _declaringTypeName.Value) is not null) + { + return normalizedName; + } + + var resultName = $"{normalizedName[..^responseSuffix.Length]}Result"; + var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; + // Model and enum files share a flat output directory, even across namespaces. + return inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace)) || + inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace)) || + inputNamespace.Clients.Any(HasConflictingClientName) + ? normalizedName + : resultName; + + bool HasConflictingName(InputType inputType, string inputTypeNamespace) + { + if (inputType == _inputType) + { + return false; + } + + var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); + var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); + var customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, otherName); + if (customType is null && !inputType.IsExactName) + { + var normalizedOtherName = otherName.NormalizeCSharpAcronyms(); + customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, normalizedOtherName); + if (sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null) + { + otherName = normalizedOtherName; + } + } + + return string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase) || + string.Equals(customType?.Name, resultName, StringComparison.OrdinalIgnoreCase); + } + + bool HasConflictingClientName(InputClient client) + { + var clientNamespace = string.IsNullOrEmpty(client.Namespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(client.Namespace); + var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); + var customType = sourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); + return (customType?.Type.Namespace ?? clientNamespace) == typeNamespace && + (customType?.Name ?? clientName) == resultName; + } + } + /// /// Resets only the cached methods so they are rebuilt on next access. /// Use this instead of when you need to force a method 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 01433f7cf40..2bf5ec5402e 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 @@ -204,20 +204,35 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.IsNotNull(model.CustomCodeView); } - [TestCase(false, false)] - [TestCase(false, true)] - [TestCase(true, false)] - [TestCase(true, true)] - public void TestBuildName_ResponseSuffixAvoidsModelCollision(bool reverseOrder, bool differentNamespace) + [TestCase(false, false, "WidgetResult", false)] + [TestCase(false, true, "WidgetResult", false)] + [TestCase(true, false, "WidgetResult", false)] + [TestCase(true, true, "WidgetResult", false)] + [TestCase(false, false, "widgetResult", true)] + public void TestBuildName_ResponseSuffixAvoidsModelCollision( + bool reverseOrder, bool differentNamespace, string resultName, bool isExactName) { var response = InputFactory.Model("WidgetResponse"); - var result = InputFactory.Model("WidgetResult", @namespace: differentNamespace ? "Other" : "Sample.Models"); + var result = InputFactory.Model( + resultName, @namespace: differentNamespace ? "Other" : "Sample.Models", isExactName: isExactName); InputModelType[] models = reverseOrder ? [result, response] : [response, result]; MockHelpers.LoadMockGenerator(inputModelTypes: models); var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); - CollectionAssert.AreEquivalent(new[] { "WidgetResponse", "WidgetResult" }, providers.Select(p => p.Name)); + CollectionAssert.AreEquivalent(new[] { "WidgetResponse", resultName }, providers.Select(p => p.Name)); + } + + [TestCase("Sample.Models", "WidgetResponse")] + [TestCase("Other", "WidgetResult")] + public void TestBuildName_ResponseSuffixAvoidsClientCollision(string clientNamespace, string expectedName) + { + var response = InputFactory.Model("WidgetResponse"); + var client = InputFactory.Client("WidgetResult"); + client.Update(@namespace: clientNamespace); + MockHelpers.LoadMockGenerator(inputModelTypes: [response], inputClients: [client]); + + Assert.AreEqual(expectedName, CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); } [TestCase(false)] diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenAsyncCollectionResult.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenAsyncCollectionResult.cs index 850e32848fd..5f90a26974c 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenAsyncCollectionResult.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenAsyncCollectionResult.cs @@ -41,7 +41,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() ClientResult result = await GetNextResponseAsync(message).ConfigureAwait(false); yield return result; - nextToken = ((ListWithContinuationTokenResponse)result).NextToken; + nextToken = ((ListWithContinuationTokenResult)result).NextToken; if (string.IsNullOrEmpty(nextToken)) { yield break; @@ -55,7 +55,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - string nextPage = ((ListWithContinuationTokenResponse)page).NextToken; + string nextPage = ((ListWithContinuationTokenResult)page).NextToken; if (!string.IsNullOrEmpty(nextPage)) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenAsyncCollectionResultOfT.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenAsyncCollectionResultOfT.cs index 9c3d3170ab2..8d9683bab41 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenAsyncCollectionResultOfT.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenAsyncCollectionResultOfT.cs @@ -41,7 +41,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() ClientResult result = await GetNextResponseAsync(message).ConfigureAwait(false); yield return result; - nextToken = ((ListWithContinuationTokenResponse)result).NextToken; + nextToken = ((ListWithContinuationTokenResult)result).NextToken; if (string.IsNullOrEmpty(nextToken)) { yield break; @@ -55,7 +55,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - string nextPage = ((ListWithContinuationTokenResponse)page).NextToken; + string nextPage = ((ListWithContinuationTokenResult)page).NextToken; if (!string.IsNullOrEmpty(nextPage)) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); @@ -71,7 +71,7 @@ public override ContinuationToken GetContinuationToken(ClientResult page) /// The values from the specified page. protected override async IAsyncEnumerable GetValuesFromPageAsync(ClientResult page) { - foreach (Thing item in ((ListWithContinuationTokenResponse)page).Things) + foreach (Thing item in ((ListWithContinuationTokenResult)page).Things) { yield return item; await Task.Yield(); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenCollectionResult.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenCollectionResult.cs index b1cfc1098f8..da17822ee6c 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenCollectionResult.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenCollectionResult.cs @@ -40,7 +40,7 @@ public override IEnumerable GetRawPages() ClientResult result = GetNextResponse(message); yield return result; - nextToken = ((ListWithContinuationTokenResponse)result).NextToken; + nextToken = ((ListWithContinuationTokenResult)result).NextToken; if (string.IsNullOrEmpty(nextToken)) { yield break; @@ -54,7 +54,7 @@ public override IEnumerable GetRawPages() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - string nextPage = ((ListWithContinuationTokenResponse)page).NextToken; + string nextPage = ((ListWithContinuationTokenResult)page).NextToken; if (!string.IsNullOrEmpty(nextPage)) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenCollectionResultOfT.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenCollectionResultOfT.cs index 69acd134fc1..ab20b9a9354 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenCollectionResultOfT.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenCollectionResultOfT.cs @@ -40,7 +40,7 @@ public override IEnumerable GetRawPages() ClientResult result = GetNextResponse(message); yield return result; - nextToken = ((ListWithContinuationTokenResponse)result).NextToken; + nextToken = ((ListWithContinuationTokenResult)result).NextToken; if (string.IsNullOrEmpty(nextToken)) { yield break; @@ -54,7 +54,7 @@ public override IEnumerable GetRawPages() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - string nextPage = ((ListWithContinuationTokenResponse)page).NextToken; + string nextPage = ((ListWithContinuationTokenResult)page).NextToken; if (!string.IsNullOrEmpty(nextPage)) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); @@ -70,7 +70,7 @@ public override ContinuationToken GetContinuationToken(ClientResult page) /// The values from the specified page. protected override IEnumerable GetValuesFromPage(ClientResult page) { - return ((ListWithContinuationTokenResponse)page).Things; + return ((ListWithContinuationTokenResult)page).Things; } /// Sends the request in the pipeline message and returns the response. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenHeaderResponseAsyncCollectionResultOfT.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenHeaderResponseAsyncCollectionResultOfT.cs index c4a863befcb..e234c85759e 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenHeaderResponseAsyncCollectionResultOfT.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenHeaderResponseAsyncCollectionResultOfT.cs @@ -73,7 +73,7 @@ public override ContinuationToken GetContinuationToken(ClientResult page) /// The values from the specified page. protected override async IAsyncEnumerable GetValuesFromPageAsync(ClientResult page) { - foreach (Thing item in ((ListWithContinuationTokenHeaderResponseResponse)page).Things) + foreach (Thing item in ((ListWithContinuationTokenHeaderResponseResult)page).Things) { yield return item; await Task.Yield(); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenHeaderResponseCollectionResultOfT.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenHeaderResponseCollectionResultOfT.cs index af88f217ecb..bc0cbbc2e7d 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenHeaderResponseCollectionResultOfT.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithContinuationTokenHeaderResponseCollectionResultOfT.cs @@ -72,7 +72,7 @@ public override ContinuationToken GetContinuationToken(ClientResult page) /// The values from the specified page. protected override IEnumerable GetValuesFromPage(ClientResult page) { - return ((ListWithContinuationTokenHeaderResponseResponse)page).Things; + return ((ListWithContinuationTokenHeaderResponseResult)page).Things; } /// Sends the request in the pipeline message and returns the response. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkAsyncCollectionResult.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkAsyncCollectionResult.cs index fcbb4f3315d..b1a35c06dc3 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkAsyncCollectionResult.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkAsyncCollectionResult.cs @@ -38,7 +38,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() ClientResult result = await GetNextResponseAsync(message).ConfigureAwait(false); yield return result; - nextPageUri = ((ListWithNextLinkResponse)result).Next; + nextPageUri = ((ListWithNextLinkResult)result).Next; if (nextPageUri == null) { yield break; @@ -52,7 +52,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - Uri nextPage = ((ListWithNextLinkResponse)page).Next; + Uri nextPage = ((ListWithNextLinkResult)page).Next; if (nextPage != null) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage.IsAbsoluteUri ? nextPage.AbsoluteUri : nextPage.OriginalString)); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkAsyncCollectionResultOfT.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkAsyncCollectionResultOfT.cs index 366d3aac566..77f0df84b9f 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkAsyncCollectionResultOfT.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkAsyncCollectionResultOfT.cs @@ -38,7 +38,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() ClientResult result = await GetNextResponseAsync(message).ConfigureAwait(false); yield return result; - nextPageUri = ((ListWithNextLinkResponse)result).Next; + nextPageUri = ((ListWithNextLinkResult)result).Next; if (nextPageUri == null) { yield break; @@ -52,7 +52,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - Uri nextPage = ((ListWithNextLinkResponse)page).Next; + Uri nextPage = ((ListWithNextLinkResult)page).Next; if (nextPage != null) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage.IsAbsoluteUri ? nextPage.AbsoluteUri : nextPage.OriginalString)); @@ -68,7 +68,7 @@ public override ContinuationToken GetContinuationToken(ClientResult page) /// The values from the specified page. protected override async IAsyncEnumerable GetValuesFromPageAsync(ClientResult page) { - foreach (Thing item in ((ListWithNextLinkResponse)page).Things) + foreach (Thing item in ((ListWithNextLinkResult)page).Things) { yield return item; await Task.Yield(); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkCollectionResult.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkCollectionResult.cs index a4f3f2aca9e..e9b516f5892 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkCollectionResult.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkCollectionResult.cs @@ -37,7 +37,7 @@ public override IEnumerable GetRawPages() ClientResult result = GetNextResponse(message); yield return result; - nextPageUri = ((ListWithNextLinkResponse)result).Next; + nextPageUri = ((ListWithNextLinkResult)result).Next; if (nextPageUri == null) { yield break; @@ -51,7 +51,7 @@ public override IEnumerable GetRawPages() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - Uri nextPage = ((ListWithNextLinkResponse)page).Next; + Uri nextPage = ((ListWithNextLinkResult)page).Next; if (nextPage != null) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage.IsAbsoluteUri ? nextPage.AbsoluteUri : nextPage.OriginalString)); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkCollectionResultOfT.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkCollectionResultOfT.cs index 2384f493d80..651d9e3487f 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkCollectionResultOfT.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithNextLinkCollectionResultOfT.cs @@ -37,7 +37,7 @@ public override IEnumerable GetRawPages() ClientResult result = GetNextResponse(message); yield return result; - nextPageUri = ((ListWithNextLinkResponse)result).Next; + nextPageUri = ((ListWithNextLinkResult)result).Next; if (nextPageUri == null) { yield break; @@ -51,7 +51,7 @@ public override IEnumerable GetRawPages() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - Uri nextPage = ((ListWithNextLinkResponse)page).Next; + Uri nextPage = ((ListWithNextLinkResult)page).Next; if (nextPage != null) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage.IsAbsoluteUri ? nextPage.AbsoluteUri : nextPage.OriginalString)); @@ -67,7 +67,7 @@ public override ContinuationToken GetContinuationToken(ClientResult page) /// The values from the specified page. protected override IEnumerable GetValuesFromPage(ClientResult page) { - return ((ListWithNextLinkResponse)page).Things; + return ((ListWithNextLinkResult)page).Things; } /// Sends the request in the pipeline message and returns the response. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkAsyncCollectionResult.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkAsyncCollectionResult.cs index 754698b6891..f41c7b3d264 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkAsyncCollectionResult.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkAsyncCollectionResult.cs @@ -38,7 +38,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() ClientResult result = await GetNextResponseAsync(message).ConfigureAwait(false); yield return result; - string nextPageString = ((ListWithStringNextLinkResponse)result).Next; + string nextPageString = ((ListWithStringNextLinkResult)result).Next; if (string.IsNullOrEmpty(nextPageString)) { yield break; @@ -53,7 +53,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - string nextPage = ((ListWithStringNextLinkResponse)page).Next; + string nextPage = ((ListWithStringNextLinkResult)page).Next; if (!string.IsNullOrEmpty(nextPage)) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkAsyncCollectionResultOfT.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkAsyncCollectionResultOfT.cs index b777d15a10a..7f48abdf7f7 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkAsyncCollectionResultOfT.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkAsyncCollectionResultOfT.cs @@ -38,7 +38,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() ClientResult result = await GetNextResponseAsync(message).ConfigureAwait(false); yield return result; - string nextPageString = ((ListWithStringNextLinkResponse)result).Next; + string nextPageString = ((ListWithStringNextLinkResult)result).Next; if (string.IsNullOrEmpty(nextPageString)) { yield break; @@ -53,7 +53,7 @@ public override async IAsyncEnumerable GetRawPagesAsync() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - string nextPage = ((ListWithStringNextLinkResponse)page).Next; + string nextPage = ((ListWithStringNextLinkResult)page).Next; if (!string.IsNullOrEmpty(nextPage)) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); @@ -69,7 +69,7 @@ public override ContinuationToken GetContinuationToken(ClientResult page) /// The values from the specified page. protected override async IAsyncEnumerable GetValuesFromPageAsync(ClientResult page) { - foreach (Thing item in ((ListWithStringNextLinkResponse)page).Things) + foreach (Thing item in ((ListWithStringNextLinkResult)page).Things) { yield return item; await Task.Yield(); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkCollectionResult.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkCollectionResult.cs index 5402f8cac11..1da5ae62fa4 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkCollectionResult.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkCollectionResult.cs @@ -37,7 +37,7 @@ public override IEnumerable GetRawPages() ClientResult result = GetNextResponse(message); yield return result; - string nextPageString = ((ListWithStringNextLinkResponse)result).Next; + string nextPageString = ((ListWithStringNextLinkResult)result).Next; if (string.IsNullOrEmpty(nextPageString)) { yield break; @@ -52,7 +52,7 @@ public override IEnumerable GetRawPages() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - string nextPage = ((ListWithStringNextLinkResponse)page).Next; + string nextPage = ((ListWithStringNextLinkResult)page).Next; if (!string.IsNullOrEmpty(nextPage)) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkCollectionResultOfT.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkCollectionResultOfT.cs index 3eae436b639..462d2612bed 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkCollectionResultOfT.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/CollectionResults/SampleTypeSpecClientGetWithStringNextLinkCollectionResultOfT.cs @@ -37,7 +37,7 @@ public override IEnumerable GetRawPages() ClientResult result = GetNextResponse(message); yield return result; - string nextPageString = ((ListWithStringNextLinkResponse)result).Next; + string nextPageString = ((ListWithStringNextLinkResult)result).Next; if (string.IsNullOrEmpty(nextPageString)) { yield break; @@ -52,7 +52,7 @@ public override IEnumerable GetRawPages() /// The continuation token for the specified page. public override ContinuationToken GetContinuationToken(ClientResult page) { - string nextPage = ((ListWithStringNextLinkResponse)page).Next; + string nextPage = ((ListWithStringNextLinkResult)page).Next; if (!string.IsNullOrEmpty(nextPage)) { return ContinuationToken.FromBytes(BinaryData.FromString(nextPage)); @@ -68,7 +68,7 @@ public override ContinuationToken GetContinuationToken(ClientResult page) /// The values from the specified page. protected override IEnumerable GetValuesFromPage(ClientResult page) { - return ((ListWithStringNextLinkResponse)page).Things; + return ((ListWithStringNextLinkResult)page).Things; } /// Sends the request in the pipeline message and returns the response. diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.cs index dab61b07de2..28bf5903d4a 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Metrics.cs @@ -164,20 +164,20 @@ public virtual async Task GetWidgetMetricsAsync(string day, Reques /// /// The cancellation token that can be used to cancel the operation. /// Service returned a non-success status code. - public virtual ClientResult GetWidgetMetrics(DaysOfWeekExtensibleEnum day, CancellationToken cancellationToken = default) + public virtual ClientResult GetWidgetMetrics(DaysOfWeekExtensibleEnum day, CancellationToken cancellationToken = default) { ClientResult result = GetWidgetMetrics(day.ToString(), cancellationToken.ToRequestOptions()); - return ClientResult.FromValue((GetWidgetMetricsResponse)result, result.GetRawResponse()); + return ClientResult.FromValue((GetWidgetMetricsResult)result, result.GetRawResponse()); } /// Get Widget metrics for given day of week. /// /// The cancellation token that can be used to cancel the operation. /// Service returned a non-success status code. - public virtual async Task> GetWidgetMetricsAsync(DaysOfWeekExtensibleEnum day, CancellationToken cancellationToken = default) + public virtual async Task> GetWidgetMetricsAsync(DaysOfWeekExtensibleEnum day, CancellationToken cancellationToken = default) { ClientResult result = await GetWidgetMetricsAsync(day.ToString(), cancellationToken.ToRequestOptions()).ConfigureAwait(false); - return ClientResult.FromValue((GetWidgetMetricsResponse)result, result.GetRawResponse()); + return ClientResult.FromValue((GetWidgetMetricsResult)result, result.GetRawResponse()); } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResult.Serialization.cs similarity index 68% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResponse.Serialization.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResult.Serialization.cs index 78120dd38a3..14a86aef841 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResult.Serialization.cs @@ -13,65 +13,65 @@ namespace SampleTypeSpec { - /// The GetNotebookResponse. - public partial class GetNotebookResponse : IJsonModel + /// The GetNotebookResult. + public partial class GetNotebookResult : IJsonModel { - /// Initializes a new instance of for deserialization. - internal GetNotebookResponse() + /// Initializes a new instance of for deserialization. + internal GetNotebookResult() { } /// The data to parse. /// The client options for reading and writing models. - protected virtual GetNotebookResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + protected virtual GetNotebookResult PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": using (JsonDocument document = JsonDocument.Parse(data, ModelSerializationExtensions.JsonDocumentOptions)) { - return DeserializeGetNotebookResponse(document.RootElement, options); + return DeserializeGetNotebookResult(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(GetNotebookResponse)} does not support reading '{options.Format}' format."); + throw new FormatException($"The model {nameof(GetNotebookResult)} does not support reading '{options.Format}' format."); } } /// The client options for reading and writing models. protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": return ModelReaderWriter.Write(this, options, SampleTypeSpecContext.Default); default: - throw new FormatException($"The model {nameof(GetNotebookResponse)} does not support writing '{options.Format}' format."); + throw new FormatException($"The model {nameof(GetNotebookResult)} does not support writing '{options.Format}' format."); } } /// The client options for reading and writing models. - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); /// The data to parse. /// The client options for reading and writing models. - GetNotebookResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + GetNotebookResult IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); /// The client options for reading and writing models. - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - /// The to deserialize the from. - public static explicit operator GetNotebookResponse(ClientResult result) + /// The to deserialize the from. + public static explicit operator GetNotebookResult(ClientResult result) { PipelineResponse response = result.GetRawResponse(); using JsonDocument document = JsonDocument.Parse(response.Content, ModelSerializationExtensions.JsonDocumentOptions); - return DeserializeGetNotebookResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + return DeserializeGetNotebookResult(document.RootElement, ModelSerializationExtensions.WireOptions); } /// The JSON writer. /// The client options for reading and writing models. - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { writer.WriteStartObject(); JsonModelWriteCore(writer, options); @@ -82,10 +82,10 @@ void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWri /// The client options for reading and writing models. protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(GetNotebookResponse)} does not support writing '{format}' format."); + throw new FormatException($"The model {nameof(GetNotebookResult)} does not support writing '{format}' format."); } writer.WritePropertyName("name"u8); writer.WriteStringValue(Name); @@ -110,24 +110,24 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit /// The JSON reader. /// The client options for reading and writing models. - GetNotebookResponse IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + GetNotebookResult IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); /// The JSON reader. /// The client options for reading and writing models. - protected virtual GetNotebookResponse JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + protected virtual GetNotebookResult JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(GetNotebookResponse)} does not support reading '{format}' format."); + throw new FormatException($"The model {nameof(GetNotebookResult)} does not support reading '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeGetNotebookResponse(document.RootElement, options); + return DeserializeGetNotebookResult(document.RootElement, options); } /// The JSON element to deserialize. /// The client options for reading and writing models. - internal static GetNotebookResponse DeserializeGetNotebookResponse(JsonElement element, ModelReaderWriterOptions options) + internal static GetNotebookResult DeserializeGetNotebookResult(JsonElement element, ModelReaderWriterOptions options) { if (element.ValueKind == JsonValueKind.Null) { @@ -153,7 +153,7 @@ internal static GetNotebookResponse DeserializeGetNotebookResponse(JsonElement e additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); } } - return new GetNotebookResponse(name, content, additionalBinaryDataProperties); + return new GetNotebookResult(name, content, additionalBinaryDataProperties); } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResult.cs similarity index 77% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResponse.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResult.cs index af0f633f72a..869f6b3a7cd 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetNotebookResult.cs @@ -10,26 +10,26 @@ namespace SampleTypeSpec { - /// The GetNotebookResponse. - public partial class GetNotebookResponse + /// The GetNotebookResult. + public partial class GetNotebookResult { /// Keeps track of any properties unknown to the library. private protected readonly IDictionary _additionalBinaryDataProperties; - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// - internal GetNotebookResponse(string name, string content) + internal GetNotebookResult(string name, string content) { Name = name; Content = content; } - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// /// Keeps track of any properties unknown to the library. - internal GetNotebookResponse(string name, string content, IDictionary additionalBinaryDataProperties) + internal GetNotebookResult(string name, string content, IDictionary additionalBinaryDataProperties) { Name = name; Content = content; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResult.Serialization.cs similarity index 67% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResponse.Serialization.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResult.Serialization.cs index a0c21a889d3..732ec427356 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResult.Serialization.cs @@ -13,65 +13,65 @@ namespace SampleTypeSpec { - /// The GetWidgetMetricsResponse. - public partial class GetWidgetMetricsResponse : IJsonModel + /// The GetWidgetMetricsResult. + public partial class GetWidgetMetricsResult : IJsonModel { - /// Initializes a new instance of for deserialization. - internal GetWidgetMetricsResponse() + /// Initializes a new instance of for deserialization. + internal GetWidgetMetricsResult() { } /// The data to parse. /// The client options for reading and writing models. - protected virtual GetWidgetMetricsResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + protected virtual GetWidgetMetricsResult PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": using (JsonDocument document = JsonDocument.Parse(data, ModelSerializationExtensions.JsonDocumentOptions)) { - return DeserializeGetWidgetMetricsResponse(document.RootElement, options); + return DeserializeGetWidgetMetricsResult(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(GetWidgetMetricsResponse)} does not support reading '{options.Format}' format."); + throw new FormatException($"The model {nameof(GetWidgetMetricsResult)} does not support reading '{options.Format}' format."); } } /// The client options for reading and writing models. protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": return ModelReaderWriter.Write(this, options, SampleTypeSpecContext.Default); default: - throw new FormatException($"The model {nameof(GetWidgetMetricsResponse)} does not support writing '{options.Format}' format."); + throw new FormatException($"The model {nameof(GetWidgetMetricsResult)} does not support writing '{options.Format}' format."); } } /// The client options for reading and writing models. - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); /// The data to parse. /// The client options for reading and writing models. - GetWidgetMetricsResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + GetWidgetMetricsResult IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); /// The client options for reading and writing models. - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - /// The to deserialize the from. - public static explicit operator GetWidgetMetricsResponse(ClientResult result) + /// The to deserialize the from. + public static explicit operator GetWidgetMetricsResult(ClientResult result) { PipelineResponse response = result.GetRawResponse(); using JsonDocument document = JsonDocument.Parse(response.Content, ModelSerializationExtensions.JsonDocumentOptions); - return DeserializeGetWidgetMetricsResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + return DeserializeGetWidgetMetricsResult(document.RootElement, ModelSerializationExtensions.WireOptions); } /// The JSON writer. /// The client options for reading and writing models. - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { writer.WriteStartObject(); JsonModelWriteCore(writer, options); @@ -82,10 +82,10 @@ void IJsonModel.Write(Utf8JsonWriter writer, ModelRead /// The client options for reading and writing models. protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(GetWidgetMetricsResponse)} does not support writing '{format}' format."); + throw new FormatException($"The model {nameof(GetWidgetMetricsResult)} does not support writing '{format}' format."); } writer.WritePropertyName("numSold"u8); writer.WriteNumberValue(NumSold); @@ -110,24 +110,24 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit /// The JSON reader. /// The client options for reading and writing models. - GetWidgetMetricsResponse IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + GetWidgetMetricsResult IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); /// The JSON reader. /// The client options for reading and writing models. - protected virtual GetWidgetMetricsResponse JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + protected virtual GetWidgetMetricsResult JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(GetWidgetMetricsResponse)} does not support reading '{format}' format."); + throw new FormatException($"The model {nameof(GetWidgetMetricsResult)} does not support reading '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeGetWidgetMetricsResponse(document.RootElement, options); + return DeserializeGetWidgetMetricsResult(document.RootElement, options); } /// The JSON element to deserialize. /// The client options for reading and writing models. - internal static GetWidgetMetricsResponse DeserializeGetWidgetMetricsResponse(JsonElement element, ModelReaderWriterOptions options) + internal static GetWidgetMetricsResult DeserializeGetWidgetMetricsResult(JsonElement element, ModelReaderWriterOptions options) { if (element.ValueKind == JsonValueKind.Null) { @@ -153,7 +153,7 @@ internal static GetWidgetMetricsResponse DeserializeGetWidgetMetricsResponse(Jso additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); } } - return new GetWidgetMetricsResponse(numSold, averagePrice, additionalBinaryDataProperties); + return new GetWidgetMetricsResult(numSold, averagePrice, additionalBinaryDataProperties); } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResult.cs similarity index 76% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResponse.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResult.cs index ddfc7b1530f..33ce5fee5b3 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/GetWidgetMetricsResult.cs @@ -10,26 +10,26 @@ namespace SampleTypeSpec { - /// The GetWidgetMetricsResponse. - public partial class GetWidgetMetricsResponse + /// The GetWidgetMetricsResult. + public partial class GetWidgetMetricsResult { /// Keeps track of any properties unknown to the library. private protected readonly IDictionary _additionalBinaryDataProperties; - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// - internal GetWidgetMetricsResponse(int numSold, float averagePrice) + internal GetWidgetMetricsResult(int numSold, float averagePrice) { NumSold = numSold; AveragePrice = averagePrice; } - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// /// Keeps track of any properties unknown to the library. - internal GetWidgetMetricsResponse(int numSold, float averagePrice, IDictionary additionalBinaryDataProperties) + internal GetWidgetMetricsResult(int numSold, float averagePrice, IDictionary additionalBinaryDataProperties) { NumSold = numSold; AveragePrice = averagePrice; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResult.Serialization.cs similarity index 72% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.Serialization.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResult.Serialization.cs index 62581e9ac00..c129339bb0a 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResult.Serialization.cs @@ -13,65 +13,65 @@ namespace SampleTypeSpec { - /// The ListWithContinuationTokenHeaderResponseResponse. - internal partial class ListWithContinuationTokenHeaderResponseResponse : IJsonModel + /// The ListWithContinuationTokenHeaderResponseResult. + internal partial class ListWithContinuationTokenHeaderResponseResult : IJsonModel { - /// Initializes a new instance of for deserialization. - internal ListWithContinuationTokenHeaderResponseResponse() + /// Initializes a new instance of for deserialization. + internal ListWithContinuationTokenHeaderResponseResult() { } /// The data to parse. /// The client options for reading and writing models. - protected virtual ListWithContinuationTokenHeaderResponseResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + protected virtual ListWithContinuationTokenHeaderResponseResult PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": using (JsonDocument document = JsonDocument.Parse(data, ModelSerializationExtensions.JsonDocumentOptions)) { - return DeserializeListWithContinuationTokenHeaderResponseResponse(document.RootElement, options); + return DeserializeListWithContinuationTokenHeaderResponseResult(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(ListWithContinuationTokenHeaderResponseResponse)} does not support reading '{options.Format}' format."); + throw new FormatException($"The model {nameof(ListWithContinuationTokenHeaderResponseResult)} does not support reading '{options.Format}' format."); } } /// The client options for reading and writing models. protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": return ModelReaderWriter.Write(this, options, SampleTypeSpecContext.Default); default: - throw new FormatException($"The model {nameof(ListWithContinuationTokenHeaderResponseResponse)} does not support writing '{options.Format}' format."); + throw new FormatException($"The model {nameof(ListWithContinuationTokenHeaderResponseResult)} does not support writing '{options.Format}' format."); } } /// The client options for reading and writing models. - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); /// The data to parse. /// The client options for reading and writing models. - ListWithContinuationTokenHeaderResponseResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + ListWithContinuationTokenHeaderResponseResult IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); /// The client options for reading and writing models. - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - /// The to deserialize the from. - public static explicit operator ListWithContinuationTokenHeaderResponseResponse(ClientResult result) + /// The to deserialize the from. + public static explicit operator ListWithContinuationTokenHeaderResponseResult(ClientResult result) { PipelineResponse response = result.GetRawResponse(); using JsonDocument document = JsonDocument.Parse(response.Content, ModelSerializationExtensions.JsonDocumentOptions); - return DeserializeListWithContinuationTokenHeaderResponseResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + return DeserializeListWithContinuationTokenHeaderResponseResult(document.RootElement, ModelSerializationExtensions.WireOptions); } /// The JSON writer. /// The client options for reading and writing models. - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { writer.WriteStartObject(); JsonModelWriteCore(writer, options); @@ -82,10 +82,10 @@ void IJsonModel.Write(Utf8JsonW /// The client options for reading and writing models. protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(ListWithContinuationTokenHeaderResponseResponse)} does not support writing '{format}' format."); + throw new FormatException($"The model {nameof(ListWithContinuationTokenHeaderResponseResult)} does not support writing '{format}' format."); } writer.WritePropertyName("things"u8); writer.WriteStartArray(); @@ -113,24 +113,24 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit /// The JSON reader. /// The client options for reading and writing models. - ListWithContinuationTokenHeaderResponseResponse IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + ListWithContinuationTokenHeaderResponseResult IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); /// The JSON reader. /// The client options for reading and writing models. - protected virtual ListWithContinuationTokenHeaderResponseResponse JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + protected virtual ListWithContinuationTokenHeaderResponseResult JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(ListWithContinuationTokenHeaderResponseResponse)} does not support reading '{format}' format."); + throw new FormatException($"The model {nameof(ListWithContinuationTokenHeaderResponseResult)} does not support reading '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeListWithContinuationTokenHeaderResponseResponse(document.RootElement, options); + return DeserializeListWithContinuationTokenHeaderResponseResult(document.RootElement, options); } /// The JSON element to deserialize. /// The client options for reading and writing models. - internal static ListWithContinuationTokenHeaderResponseResponse DeserializeListWithContinuationTokenHeaderResponseResponse(JsonElement element, ModelReaderWriterOptions options) + internal static ListWithContinuationTokenHeaderResponseResult DeserializeListWithContinuationTokenHeaderResponseResult(JsonElement element, ModelReaderWriterOptions options) { if (element.ValueKind == JsonValueKind.Null) { @@ -155,7 +155,7 @@ internal static ListWithContinuationTokenHeaderResponseResponse DeserializeListW additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); } } - return new ListWithContinuationTokenHeaderResponseResponse(things, additionalBinaryDataProperties); + return new ListWithContinuationTokenHeaderResponseResult(things, additionalBinaryDataProperties); } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResult.cs similarity index 74% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResult.cs index dc281b42e3d..45745f85a95 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenHeaderResponseResult.cs @@ -11,23 +11,23 @@ namespace SampleTypeSpec { - /// The ListWithContinuationTokenHeaderResponseResponse. - internal partial class ListWithContinuationTokenHeaderResponseResponse + /// The ListWithContinuationTokenHeaderResponseResult. + internal partial class ListWithContinuationTokenHeaderResponseResult { /// Keeps track of any properties unknown to the library. private protected readonly IDictionary _additionalBinaryDataProperties; - /// Initializes a new instance of . + /// Initializes a new instance of . /// - internal ListWithContinuationTokenHeaderResponseResponse(IEnumerable things) + internal ListWithContinuationTokenHeaderResponseResult(IEnumerable things) { Things = things.ToList(); } - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// Keeps track of any properties unknown to the library. - internal ListWithContinuationTokenHeaderResponseResponse(IList things, IDictionary additionalBinaryDataProperties) + internal ListWithContinuationTokenHeaderResponseResult(IList things, IDictionary additionalBinaryDataProperties) { Things = things; _additionalBinaryDataProperties = additionalBinaryDataProperties; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResult.Serialization.cs similarity index 69% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.Serialization.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResult.Serialization.cs index 3f1f22f975f..9c60cc4d9c9 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResult.Serialization.cs @@ -13,65 +13,65 @@ namespace SampleTypeSpec { - /// The ListWithContinuationTokenResponse. - internal partial class ListWithContinuationTokenResponse : IJsonModel + /// The ListWithContinuationTokenResult. + internal partial class ListWithContinuationTokenResult : IJsonModel { - /// Initializes a new instance of for deserialization. - internal ListWithContinuationTokenResponse() + /// Initializes a new instance of for deserialization. + internal ListWithContinuationTokenResult() { } /// The data to parse. /// The client options for reading and writing models. - protected virtual ListWithContinuationTokenResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + protected virtual ListWithContinuationTokenResult PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": using (JsonDocument document = JsonDocument.Parse(data, ModelSerializationExtensions.JsonDocumentOptions)) { - return DeserializeListWithContinuationTokenResponse(document.RootElement, options); + return DeserializeListWithContinuationTokenResult(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(ListWithContinuationTokenResponse)} does not support reading '{options.Format}' format."); + throw new FormatException($"The model {nameof(ListWithContinuationTokenResult)} does not support reading '{options.Format}' format."); } } /// The client options for reading and writing models. protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": return ModelReaderWriter.Write(this, options, SampleTypeSpecContext.Default); default: - throw new FormatException($"The model {nameof(ListWithContinuationTokenResponse)} does not support writing '{options.Format}' format."); + throw new FormatException($"The model {nameof(ListWithContinuationTokenResult)} does not support writing '{options.Format}' format."); } } /// The client options for reading and writing models. - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); /// The data to parse. /// The client options for reading and writing models. - ListWithContinuationTokenResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + ListWithContinuationTokenResult IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); /// The client options for reading and writing models. - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - /// The to deserialize the from. - public static explicit operator ListWithContinuationTokenResponse(ClientResult result) + /// The to deserialize the from. + public static explicit operator ListWithContinuationTokenResult(ClientResult result) { PipelineResponse response = result.GetRawResponse(); using JsonDocument document = JsonDocument.Parse(response.Content, ModelSerializationExtensions.JsonDocumentOptions); - return DeserializeListWithContinuationTokenResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + return DeserializeListWithContinuationTokenResult(document.RootElement, ModelSerializationExtensions.WireOptions); } /// The JSON writer. /// The client options for reading and writing models. - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { writer.WriteStartObject(); JsonModelWriteCore(writer, options); @@ -82,10 +82,10 @@ void IJsonModel.Write(Utf8JsonWriter writer, /// The client options for reading and writing models. protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(ListWithContinuationTokenResponse)} does not support writing '{format}' format."); + throw new FormatException($"The model {nameof(ListWithContinuationTokenResult)} does not support writing '{format}' format."); } writer.WritePropertyName("things"u8); writer.WriteStartArray(); @@ -118,24 +118,24 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit /// The JSON reader. /// The client options for reading and writing models. - ListWithContinuationTokenResponse IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + ListWithContinuationTokenResult IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); /// The JSON reader. /// The client options for reading and writing models. - protected virtual ListWithContinuationTokenResponse JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + protected virtual ListWithContinuationTokenResult JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(ListWithContinuationTokenResponse)} does not support reading '{format}' format."); + throw new FormatException($"The model {nameof(ListWithContinuationTokenResult)} does not support reading '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeListWithContinuationTokenResponse(document.RootElement, options); + return DeserializeListWithContinuationTokenResult(document.RootElement, options); } /// The JSON element to deserialize. /// The client options for reading and writing models. - internal static ListWithContinuationTokenResponse DeserializeListWithContinuationTokenResponse(JsonElement element, ModelReaderWriterOptions options) + internal static ListWithContinuationTokenResult DeserializeListWithContinuationTokenResult(JsonElement element, ModelReaderWriterOptions options) { if (element.ValueKind == JsonValueKind.Null) { @@ -166,7 +166,7 @@ internal static ListWithContinuationTokenResponse DeserializeListWithContinuatio additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); } } - return new ListWithContinuationTokenResponse(things, nextToken, additionalBinaryDataProperties); + return new ListWithContinuationTokenResult(things, nextToken, additionalBinaryDataProperties); } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResult.cs similarity index 72% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResult.cs index e689ab9f9bd..38a72b954b3 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithContinuationTokenResult.cs @@ -11,24 +11,24 @@ namespace SampleTypeSpec { - /// The ListWithContinuationTokenResponse. - internal partial class ListWithContinuationTokenResponse + /// The ListWithContinuationTokenResult. + internal partial class ListWithContinuationTokenResult { /// Keeps track of any properties unknown to the library. private protected readonly IDictionary _additionalBinaryDataProperties; - /// Initializes a new instance of . + /// Initializes a new instance of . /// - internal ListWithContinuationTokenResponse(IEnumerable things) + internal ListWithContinuationTokenResult(IEnumerable things) { Things = things.ToList(); } - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// /// Keeps track of any properties unknown to the library. - internal ListWithContinuationTokenResponse(IList things, string nextToken, IDictionary additionalBinaryDataProperties) + internal ListWithContinuationTokenResult(IList things, string nextToken, IDictionary additionalBinaryDataProperties) { Things = things; NextToken = nextToken; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResult.Serialization.cs similarity index 69% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.Serialization.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResult.Serialization.cs index 4f831017597..aeaabbeb930 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResult.Serialization.cs @@ -13,65 +13,65 @@ namespace SampleTypeSpec { - /// The ListWithNextLinkResponse. - internal partial class ListWithNextLinkResponse : IJsonModel + /// The ListWithNextLinkResult. + internal partial class ListWithNextLinkResult : IJsonModel { - /// Initializes a new instance of for deserialization. - internal ListWithNextLinkResponse() + /// Initializes a new instance of for deserialization. + internal ListWithNextLinkResult() { } /// The data to parse. /// The client options for reading and writing models. - protected virtual ListWithNextLinkResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + protected virtual ListWithNextLinkResult PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": using (JsonDocument document = JsonDocument.Parse(data, ModelSerializationExtensions.JsonDocumentOptions)) { - return DeserializeListWithNextLinkResponse(document.RootElement, options); + return DeserializeListWithNextLinkResult(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(ListWithNextLinkResponse)} does not support reading '{options.Format}' format."); + throw new FormatException($"The model {nameof(ListWithNextLinkResult)} does not support reading '{options.Format}' format."); } } /// The client options for reading and writing models. protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": return ModelReaderWriter.Write(this, options, SampleTypeSpecContext.Default); default: - throw new FormatException($"The model {nameof(ListWithNextLinkResponse)} does not support writing '{options.Format}' format."); + throw new FormatException($"The model {nameof(ListWithNextLinkResult)} does not support writing '{options.Format}' format."); } } /// The client options for reading and writing models. - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); /// The data to parse. /// The client options for reading and writing models. - ListWithNextLinkResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + ListWithNextLinkResult IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); /// The client options for reading and writing models. - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - /// The to deserialize the from. - public static explicit operator ListWithNextLinkResponse(ClientResult result) + /// The to deserialize the from. + public static explicit operator ListWithNextLinkResult(ClientResult result) { PipelineResponse response = result.GetRawResponse(); using JsonDocument document = JsonDocument.Parse(response.Content, ModelSerializationExtensions.JsonDocumentOptions); - return DeserializeListWithNextLinkResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + return DeserializeListWithNextLinkResult(document.RootElement, ModelSerializationExtensions.WireOptions); } /// The JSON writer. /// The client options for reading and writing models. - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { writer.WriteStartObject(); JsonModelWriteCore(writer, options); @@ -82,10 +82,10 @@ void IJsonModel.Write(Utf8JsonWriter writer, ModelRead /// The client options for reading and writing models. protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(ListWithNextLinkResponse)} does not support writing '{format}' format."); + throw new FormatException($"The model {nameof(ListWithNextLinkResult)} does not support writing '{format}' format."); } writer.WritePropertyName("things"u8); writer.WriteStartArray(); @@ -118,24 +118,24 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit /// The JSON reader. /// The client options for reading and writing models. - ListWithNextLinkResponse IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + ListWithNextLinkResult IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); /// The JSON reader. /// The client options for reading and writing models. - protected virtual ListWithNextLinkResponse JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + protected virtual ListWithNextLinkResult JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(ListWithNextLinkResponse)} does not support reading '{format}' format."); + throw new FormatException($"The model {nameof(ListWithNextLinkResult)} does not support reading '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeListWithNextLinkResponse(document.RootElement, options); + return DeserializeListWithNextLinkResult(document.RootElement, options); } /// The JSON element to deserialize. /// The client options for reading and writing models. - internal static ListWithNextLinkResponse DeserializeListWithNextLinkResponse(JsonElement element, ModelReaderWriterOptions options) + internal static ListWithNextLinkResult DeserializeListWithNextLinkResult(JsonElement element, ModelReaderWriterOptions options) { if (element.ValueKind == JsonValueKind.Null) { @@ -170,7 +170,7 @@ internal static ListWithNextLinkResponse DeserializeListWithNextLinkResponse(Jso additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); } } - return new ListWithNextLinkResponse(things, next, additionalBinaryDataProperties); + return new ListWithNextLinkResult(things, next, additionalBinaryDataProperties); } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResult.cs similarity index 74% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResult.cs index 96de907bd81..547603d5850 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithNextLinkResult.cs @@ -11,24 +11,24 @@ namespace SampleTypeSpec { - /// The ListWithNextLinkResponse. - internal partial class ListWithNextLinkResponse + /// The ListWithNextLinkResult. + internal partial class ListWithNextLinkResult { /// Keeps track of any properties unknown to the library. private protected readonly IDictionary _additionalBinaryDataProperties; - /// Initializes a new instance of . + /// Initializes a new instance of . /// - internal ListWithNextLinkResponse(IEnumerable things) + internal ListWithNextLinkResult(IEnumerable things) { Things = things.ToList(); } - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// /// Keeps track of any properties unknown to the library. - internal ListWithNextLinkResponse(IList things, Uri next, IDictionary additionalBinaryDataProperties) + internal ListWithNextLinkResult(IList things, Uri next, IDictionary additionalBinaryDataProperties) { Things = things; Next = next; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResult.Serialization.cs similarity index 69% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.Serialization.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResult.Serialization.cs index 15732d94656..0b346a622cd 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResult.Serialization.cs @@ -13,65 +13,65 @@ namespace SampleTypeSpec { - /// The ListWithStringNextLinkResponse. - internal partial class ListWithStringNextLinkResponse : IJsonModel + /// The ListWithStringNextLinkResult. + internal partial class ListWithStringNextLinkResult : IJsonModel { - /// Initializes a new instance of for deserialization. - internal ListWithStringNextLinkResponse() + /// Initializes a new instance of for deserialization. + internal ListWithStringNextLinkResult() { } /// The data to parse. /// The client options for reading and writing models. - protected virtual ListWithStringNextLinkResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + protected virtual ListWithStringNextLinkResult PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": using (JsonDocument document = JsonDocument.Parse(data, ModelSerializationExtensions.JsonDocumentOptions)) { - return DeserializeListWithStringNextLinkResponse(document.RootElement, options); + return DeserializeListWithStringNextLinkResult(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(ListWithStringNextLinkResponse)} does not support reading '{options.Format}' format."); + throw new FormatException($"The model {nameof(ListWithStringNextLinkResult)} does not support reading '{options.Format}' format."); } } /// The client options for reading and writing models. protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": return ModelReaderWriter.Write(this, options, SampleTypeSpecContext.Default); default: - throw new FormatException($"The model {nameof(ListWithStringNextLinkResponse)} does not support writing '{options.Format}' format."); + throw new FormatException($"The model {nameof(ListWithStringNextLinkResult)} does not support writing '{options.Format}' format."); } } /// The client options for reading and writing models. - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); /// The data to parse. /// The client options for reading and writing models. - ListWithStringNextLinkResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + ListWithStringNextLinkResult IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); /// The client options for reading and writing models. - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - /// The to deserialize the from. - public static explicit operator ListWithStringNextLinkResponse(ClientResult result) + /// The to deserialize the from. + public static explicit operator ListWithStringNextLinkResult(ClientResult result) { PipelineResponse response = result.GetRawResponse(); using JsonDocument document = JsonDocument.Parse(response.Content, ModelSerializationExtensions.JsonDocumentOptions); - return DeserializeListWithStringNextLinkResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + return DeserializeListWithStringNextLinkResult(document.RootElement, ModelSerializationExtensions.WireOptions); } /// The JSON writer. /// The client options for reading and writing models. - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { writer.WriteStartObject(); JsonModelWriteCore(writer, options); @@ -82,10 +82,10 @@ void IJsonModel.Write(Utf8JsonWriter writer, Mod /// The client options for reading and writing models. protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(ListWithStringNextLinkResponse)} does not support writing '{format}' format."); + throw new FormatException($"The model {nameof(ListWithStringNextLinkResult)} does not support writing '{format}' format."); } writer.WritePropertyName("things"u8); writer.WriteStartArray(); @@ -118,24 +118,24 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit /// The JSON reader. /// The client options for reading and writing models. - ListWithStringNextLinkResponse IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + ListWithStringNextLinkResult IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); /// The JSON reader. /// The client options for reading and writing models. - protected virtual ListWithStringNextLinkResponse JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + protected virtual ListWithStringNextLinkResult JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(ListWithStringNextLinkResponse)} does not support reading '{format}' format."); + throw new FormatException($"The model {nameof(ListWithStringNextLinkResult)} does not support reading '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeListWithStringNextLinkResponse(document.RootElement, options); + return DeserializeListWithStringNextLinkResult(document.RootElement, options); } /// The JSON element to deserialize. /// The client options for reading and writing models. - internal static ListWithStringNextLinkResponse DeserializeListWithStringNextLinkResponse(JsonElement element, ModelReaderWriterOptions options) + internal static ListWithStringNextLinkResult DeserializeListWithStringNextLinkResult(JsonElement element, ModelReaderWriterOptions options) { if (element.ValueKind == JsonValueKind.Null) { @@ -166,7 +166,7 @@ internal static ListWithStringNextLinkResponse DeserializeListWithStringNextLink additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); } } - return new ListWithStringNextLinkResponse(things, next, additionalBinaryDataProperties); + return new ListWithStringNextLinkResult(things, next, additionalBinaryDataProperties); } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResult.cs similarity index 73% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResult.cs index 38c936b133d..f20096bb119 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ListWithStringNextLinkResult.cs @@ -11,24 +11,24 @@ namespace SampleTypeSpec { - /// The ListWithStringNextLinkResponse. - internal partial class ListWithStringNextLinkResponse + /// The ListWithStringNextLinkResult. + internal partial class ListWithStringNextLinkResult { /// Keeps track of any properties unknown to the library. private protected readonly IDictionary _additionalBinaryDataProperties; - /// Initializes a new instance of . + /// Initializes a new instance of . /// - internal ListWithStringNextLinkResponse(IEnumerable things) + internal ListWithStringNextLinkResult(IEnumerable things) { Things = things.ToList(); } - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// /// Keeps track of any properties unknown to the library. - internal ListWithStringNextLinkResponse(IList things, string next, IDictionary additionalBinaryDataProperties) + internal ListWithStringNextLinkResult(IList things, string next, IDictionary additionalBinaryDataProperties) { Things = things; Next = next; diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResult.Serialization.cs similarity index 65% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResult.Serialization.cs index b3ec4a85bed..9d0371e973a 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResult.Serialization.cs @@ -13,60 +13,60 @@ namespace SampleTypeSpec { - /// The ReturnsAnonymousModelResponse. - public partial class ReturnsAnonymousModelResponse : IJsonModel + /// The ReturnsAnonymousModelResult. + public partial class ReturnsAnonymousModelResult : IJsonModel { /// The data to parse. /// The client options for reading and writing models. - protected virtual ReturnsAnonymousModelResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) + protected virtual ReturnsAnonymousModelResult PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": using (JsonDocument document = JsonDocument.Parse(data, ModelSerializationExtensions.JsonDocumentOptions)) { - return DeserializeReturnsAnonymousModelResponse(document.RootElement, options); + return DeserializeReturnsAnonymousModelResult(document.RootElement, options); } default: - throw new FormatException($"The model {nameof(ReturnsAnonymousModelResponse)} does not support reading '{options.Format}' format."); + throw new FormatException($"The model {nameof(ReturnsAnonymousModelResult)} does not support reading '{options.Format}' format."); } } /// The client options for reading and writing models. protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; switch (format) { case "J": return ModelReaderWriter.Write(this, options, SampleTypeSpecContext.Default); default: - throw new FormatException($"The model {nameof(ReturnsAnonymousModelResponse)} does not support writing '{options.Format}' format."); + throw new FormatException($"The model {nameof(ReturnsAnonymousModelResult)} does not support writing '{options.Format}' format."); } } /// The client options for reading and writing models. - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => PersistableModelWriteCore(options); /// The data to parse. /// The client options for reading and writing models. - ReturnsAnonymousModelResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); + ReturnsAnonymousModelResult IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => PersistableModelCreateCore(data, options); /// The client options for reading and writing models. - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => "J"; - /// The to deserialize the from. - public static explicit operator ReturnsAnonymousModelResponse(ClientResult result) + /// The to deserialize the from. + public static explicit operator ReturnsAnonymousModelResult(ClientResult result) { PipelineResponse response = result.GetRawResponse(); using JsonDocument document = JsonDocument.Parse(response.Content, ModelSerializationExtensions.JsonDocumentOptions); - return DeserializeReturnsAnonymousModelResponse(document.RootElement, ModelSerializationExtensions.WireOptions); + return DeserializeReturnsAnonymousModelResult(document.RootElement, ModelSerializationExtensions.WireOptions); } /// The JSON writer. /// The client options for reading and writing models. - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { writer.WriteStartObject(); JsonModelWriteCore(writer, options); @@ -77,10 +77,10 @@ void IJsonModel.Write(Utf8JsonWriter writer, Mode /// The client options for reading and writing models. protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(ReturnsAnonymousModelResponse)} does not support writing '{format}' format."); + throw new FormatException($"The model {nameof(ReturnsAnonymousModelResult)} does not support writing '{format}' format."); } if (options.Format != "W" && _additionalBinaryDataProperties != null) { @@ -101,24 +101,24 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit /// The JSON reader. /// The client options for reading and writing models. - ReturnsAnonymousModelResponse IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); + ReturnsAnonymousModelResult IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => JsonModelCreateCore(ref reader, options); /// The JSON reader. /// The client options for reading and writing models. - protected virtual ReturnsAnonymousModelResponse JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) + protected virtual ReturnsAnonymousModelResult JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { - string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + string format = options.Format == "W" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format; if (format != "J") { - throw new FormatException($"The model {nameof(ReturnsAnonymousModelResponse)} does not support reading '{format}' format."); + throw new FormatException($"The model {nameof(ReturnsAnonymousModelResult)} does not support reading '{format}' format."); } using JsonDocument document = JsonDocument.ParseValue(ref reader); - return DeserializeReturnsAnonymousModelResponse(document.RootElement, options); + return DeserializeReturnsAnonymousModelResult(document.RootElement, options); } /// The JSON element to deserialize. /// The client options for reading and writing models. - internal static ReturnsAnonymousModelResponse DeserializeReturnsAnonymousModelResponse(JsonElement element, ModelReaderWriterOptions options) + internal static ReturnsAnonymousModelResult DeserializeReturnsAnonymousModelResult(JsonElement element, ModelReaderWriterOptions options) { if (element.ValueKind == JsonValueKind.Null) { @@ -132,7 +132,7 @@ internal static ReturnsAnonymousModelResponse DeserializeReturnsAnonymousModelRe additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); } } - return new ReturnsAnonymousModelResponse(additionalBinaryDataProperties); + return new ReturnsAnonymousModelResult(additionalBinaryDataProperties); } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResult.cs similarity index 67% rename from packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.cs rename to packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResult.cs index 5e5d23873b1..f8697d817a0 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResult.cs @@ -10,20 +10,20 @@ namespace SampleTypeSpec { - /// The ReturnsAnonymousModelResponse. - public partial class ReturnsAnonymousModelResponse + /// The ReturnsAnonymousModelResult. + public partial class ReturnsAnonymousModelResult { /// Keeps track of any properties unknown to the library. private protected readonly IDictionary _additionalBinaryDataProperties; - /// Initializes a new instance of . - internal ReturnsAnonymousModelResponse() + /// Initializes a new instance of . + internal ReturnsAnonymousModelResult() { } - /// Initializes a new instance of . + /// Initializes a new instance of . /// Keeps track of any properties unknown to the library. - internal ReturnsAnonymousModelResponse(IDictionary additionalBinaryDataProperties) + internal ReturnsAnonymousModelResult(IDictionary additionalBinaryDataProperties) { _additionalBinaryDataProperties = additionalBinaryDataProperties; } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/SampleTypeSpecContext.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/SampleTypeSpecContext.cs index b58f7f1b7f7..7cf48210f0d 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/SampleTypeSpecContext.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/SampleTypeSpecContext.cs @@ -19,12 +19,12 @@ namespace SampleTypeSpec [ModelReaderWriterBuildable(typeof(Dog))] [ModelReaderWriterBuildable(typeof(DynamicModel))] [ModelReaderWriterBuildable(typeof(Friend))] - [ModelReaderWriterBuildable(typeof(GetNotebookResponse))] - [ModelReaderWriterBuildable(typeof(GetWidgetMetricsResponse))] - [ModelReaderWriterBuildable(typeof(ListWithContinuationTokenHeaderResponseResponse))] - [ModelReaderWriterBuildable(typeof(ListWithContinuationTokenResponse))] - [ModelReaderWriterBuildable(typeof(ListWithNextLinkResponse))] - [ModelReaderWriterBuildable(typeof(ListWithStringNextLinkResponse))] + [ModelReaderWriterBuildable(typeof(GetNotebookResult))] + [ModelReaderWriterBuildable(typeof(GetWidgetMetricsResult))] + [ModelReaderWriterBuildable(typeof(ListWithContinuationTokenHeaderResponseResult))] + [ModelReaderWriterBuildable(typeof(ListWithContinuationTokenResult))] + [ModelReaderWriterBuildable(typeof(ListWithNextLinkResult))] + [ModelReaderWriterBuildable(typeof(ListWithStringNextLinkResult))] [ModelReaderWriterBuildable(typeof(ModelWithEmbeddedNonBodyParameters))] [ModelReaderWriterBuildable(typeof(ModelWithRequiredNullableProperties))] [ModelReaderWriterBuildable(typeof(NullableDynamicModel))] @@ -32,7 +32,7 @@ namespace SampleTypeSpec [ModelReaderWriterBuildable(typeof(Pet))] [ModelReaderWriterBuildable(typeof(Plant))] [ModelReaderWriterBuildable(typeof(RenamedModelCustom))] - [ModelReaderWriterBuildable(typeof(ReturnsAnonymousModelResponse))] + [ModelReaderWriterBuildable(typeof(ReturnsAnonymousModelResult))] [ModelReaderWriterBuildable(typeof(RoundTripModel))] [ModelReaderWriterBuildable(typeof(StreamingItem))] [ModelReaderWriterBuildable(typeof(Thing))] diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Notebooks.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Notebooks.cs index 27cc5179a09..1fecec56e9e 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Notebooks.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Notebooks.cs @@ -161,19 +161,19 @@ public virtual async Task GetNotebookAsync(RequestOptions options) /// Get a notebook by name. /// The cancellation token that can be used to cancel the operation. /// Service returned a non-success status code. - public virtual ClientResult GetNotebook(CancellationToken cancellationToken = default) + public virtual ClientResult GetNotebook(CancellationToken cancellationToken = default) { ClientResult result = GetNotebook(cancellationToken.ToRequestOptions()); - return ClientResult.FromValue((GetNotebookResponse)result, result.GetRawResponse()); + return ClientResult.FromValue((GetNotebookResult)result, result.GetRawResponse()); } /// Get a notebook by name. /// The cancellation token that can be used to cancel the operation. /// Service returned a non-success status code. - public virtual async Task> GetNotebookAsync(CancellationToken cancellationToken = default) + public virtual async Task> GetNotebookAsync(CancellationToken cancellationToken = default) { ClientResult result = await GetNotebookAsync(cancellationToken.ToRequestOptions()).ConfigureAwait(false); - return ClientResult.FromValue((GetNotebookResponse)result, result.GetRawResponse()); + return ClientResult.FromValue((GetNotebookResult)result, result.GetRawResponse()); } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs index f9f6970f13a..b8aa6f0bd1c 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecClient.cs @@ -1031,19 +1031,19 @@ public virtual async Task ReturnsAnonymousModelAsync(RequestOption /// return anonymous model. /// The cancellation token that can be used to cancel the operation. /// Service returned a non-success status code. - public virtual ClientResult ReturnsAnonymousModel(CancellationToken cancellationToken = default) + public virtual ClientResult ReturnsAnonymousModel(CancellationToken cancellationToken = default) { ClientResult result = ReturnsAnonymousModel(cancellationToken.ToRequestOptions()); - return ClientResult.FromValue((ReturnsAnonymousModelResponse)result, result.GetRawResponse()); + return ClientResult.FromValue((ReturnsAnonymousModelResult)result, result.GetRawResponse()); } /// return anonymous model. /// The cancellation token that can be used to cancel the operation. /// Service returned a non-success status code. - public virtual async Task> ReturnsAnonymousModelAsync(CancellationToken cancellationToken = default) + public virtual async Task> ReturnsAnonymousModelAsync(CancellationToken cancellationToken = default) { ClientResult result = await ReturnsAnonymousModelAsync(cancellationToken.ToRequestOptions()).ConfigureAwait(false); - return ClientResult.FromValue((ReturnsAnonymousModelResponse)result, result.GetRawResponse()); + return ClientResult.FromValue((ReturnsAnonymousModelResult)result, result.GetRawResponse()); } /// diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs index b51746632cb..9c7b48fd7d4 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/SampleTypeSpecModelFactory.cs @@ -165,11 +165,11 @@ public static RenamedModelCustom RenamedModelCustom(string name = default, strin return new RenamedModelCustom(name, additionalBinaryDataProperties: null, otherName); } - /// The ReturnsAnonymousModelResponse. - /// A new instance for mocking. - public static ReturnsAnonymousModelResponse ReturnsAnonymousModelResponse() + /// The ReturnsAnonymousModelResult. + /// A new instance for mocking. + public static ReturnsAnonymousModelResult ReturnsAnonymousModelResult() { - return new ReturnsAnonymousModelResponse(additionalBinaryDataProperties: null); + return new ReturnsAnonymousModelResult(additionalBinaryDataProperties: null); } /// The ModelWithEmbeddedNonBodyParameters. @@ -492,22 +492,22 @@ public static Plant Plant(string species = default, string id = default, int hei return new UnknownPlant(species, id, height, additionalBinaryDataProperties: null); } - /// The GetWidgetMetricsResponse. + /// The GetWidgetMetricsResult. /// /// - /// A new instance for mocking. - public static GetWidgetMetricsResponse GetWidgetMetricsResponse(int numSold = default, float averagePrice = default) + /// A new instance for mocking. + public static GetWidgetMetricsResult GetWidgetMetricsResult(int numSold = default, float averagePrice = default) { - return new GetWidgetMetricsResponse(numSold, averagePrice, additionalBinaryDataProperties: null); + return new GetWidgetMetricsResult(numSold, averagePrice, additionalBinaryDataProperties: null); } - /// The GetNotebookResponse. + /// The GetNotebookResult. /// /// - /// A new instance for mocking. - public static GetNotebookResponse GetNotebookResponse(string name = default, string content = default) + /// A new instance for mocking. + public static GetNotebookResult GetNotebookResult(string name = default, string content = default) { - return new GetNotebookResponse(name, content, additionalBinaryDataProperties: null); + return new GetNotebookResult(name, content, additionalBinaryDataProperties: null); } /// The NullableDynamicModel. diff --git a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResponse.Serialization.cs deleted file mode 100644 index 9d2744ca516..00000000000 --- a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResponse.Serialization.cs +++ /dev/null @@ -1,36 +0,0 @@ -// - -#nullable disable - -using System; -using System.ClientModel; -using System.ClientModel.Primitives; -using System.Text.Json; - -namespace _Type.Union -{ - public partial class GetResponse : IJsonModel - { - internal GetResponse() => throw null; - - protected virtual GetResponse PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) => throw null; - - protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) => throw null; - - BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => throw null; - - GetResponse IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => throw null; - - string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => throw null; - - public static explicit operator GetResponse(ClientResult result) => throw null; - - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) => throw null; - - protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) => throw null; - - GetResponse IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => throw null; - - protected virtual GetResponse JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => throw null; - } -} diff --git a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResult.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResult.Serialization.cs new file mode 100644 index 00000000000..53be5bbecec --- /dev/null +++ b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResult.Serialization.cs @@ -0,0 +1,36 @@ +// + +#nullable disable + +using System; +using System.ClientModel; +using System.ClientModel.Primitives; +using System.Text.Json; + +namespace _Type.Union +{ + public partial class GetResult : IJsonModel + { + internal GetResult() => throw null; + + protected virtual GetResult PersistableModelCreateCore(BinaryData data, ModelReaderWriterOptions options) => throw null; + + protected virtual BinaryData PersistableModelWriteCore(ModelReaderWriterOptions options) => throw null; + + BinaryData IPersistableModel.Write(ModelReaderWriterOptions options) => throw null; + + GetResult IPersistableModel.Create(BinaryData data, ModelReaderWriterOptions options) => throw null; + + string IPersistableModel.GetFormatFromOptions(ModelReaderWriterOptions options) => throw null; + + public static explicit operator GetResult(ClientResult result) => throw null; + + void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) => throw null; + + protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWriterOptions options) => throw null; + + GetResult IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => throw null; + + protected virtual GetResult JsonModelCreateCore(ref Utf8JsonReader reader, ModelReaderWriterOptions options) => throw null; + } +} diff --git a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResponse.cs b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResult.cs similarity index 77% rename from packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResponse.cs rename to packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResult.cs index 74aff777d1c..5e9865629f0 100644 --- a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/GetResult.cs @@ -4,7 +4,7 @@ namespace _Type.Union { - public partial class GetResponse + public partial class GetResult { public GetResponseProp Prop => throw null; } diff --git a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/_TypeUnionContext.cs b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/_TypeUnionContext.cs index 60e51e84695..bdce6aa0f5d 100644 --- a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/_TypeUnionContext.cs +++ b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/Models/_TypeUnionContext.cs @@ -9,7 +9,6 @@ namespace _Type.Union [ModelReaderWriterBuildable(typeof(Cat))] [ModelReaderWriterBuildable(typeof(Dog))] [ModelReaderWriterBuildable(typeof(EnumsOnlyCases))] - [ModelReaderWriterBuildable(typeof(GetResponse))] [ModelReaderWriterBuildable(typeof(GetResponse1))] [ModelReaderWriterBuildable(typeof(GetResponse2))] [ModelReaderWriterBuildable(typeof(GetResponse3))] @@ -19,6 +18,7 @@ namespace _Type.Union [ModelReaderWriterBuildable(typeof(GetResponse7))] [ModelReaderWriterBuildable(typeof(GetResponse8))] [ModelReaderWriterBuildable(typeof(GetResponse9))] + [ModelReaderWriterBuildable(typeof(GetResult))] [ModelReaderWriterBuildable(typeof(MixedLiteralsCases))] [ModelReaderWriterBuildable(typeof(MixedTypesCases))] [ModelReaderWriterBuildable(typeof(StringAndArrayCases))] diff --git a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/StringsOnly.cs b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/StringsOnly.cs index fb8ec122f24..317c4c7ca07 100644 --- a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/StringsOnly.cs +++ b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/StringsOnly.cs @@ -22,9 +22,9 @@ public partial class StringsOnly public virtual Task GetAsync(RequestOptions options) => throw null; - public virtual ClientResult Get(CancellationToken cancellationToken = default) => throw null; + public virtual ClientResult Get(CancellationToken cancellationToken = default) => throw null; - public virtual Task> GetAsync(CancellationToken cancellationToken = default) => throw null; + public virtual Task> GetAsync(CancellationToken cancellationToken = default) => throw null; public virtual ClientResult Send(BinaryContent content, RequestOptions options = null) => throw null; diff --git a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/TypeUnionModelFactory.cs b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/TypeUnionModelFactory.cs index 94c8fc67c26..95291f26185 100644 --- a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/TypeUnionModelFactory.cs +++ b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/union/src/Generated/TypeUnionModelFactory.cs @@ -9,7 +9,7 @@ namespace _Type.Union { public static partial class TypeUnionModelFactory { - public static GetResponse GetResponse(GetResponseProp prop = default) => throw null; + public static GetResult GetResult(GetResponseProp prop = default) => throw null; public static GetResponse1 GetResponse1(GetResponseProp1 prop = default) => throw null; From d9ed5f89b115c8badddca7732dd2eca2b126e1a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 19:59:44 +0000 Subject: [PATCH 04/68] fix(csharp): account for Result-targeted customization aliases Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/TypeProvider.cs | 7 +++++++ .../ModelProviders/ModelProviderTests.cs | 16 ++++++++++++++++ .../WidgetResult.cs | 9 +++++++++ 3 files changed, 32 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsResultCustomizationAliasCollision/WidgetResult.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs index 4bf30ab77b2..5b0857c3d5a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs @@ -836,6 +836,13 @@ bool HasConflictingName(InputType inputType, string inputTypeNamespace) if (sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null) { otherName = normalizedOtherName; + if (customType is null && inputType is InputModelType && + otherName.EndsWith(responseSuffix, StringComparison.Ordinal) && + sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null) + { + customType = sourceInputModel.FindForTypeInCurrentCompilation( + otherNamespace, $"{otherName[..^responseSuffix.Length]}Result"); + } } } 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 2bf5ec5402e..b8ce1f6ab7a 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 @@ -261,6 +261,22 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(other)!.Name); } + [TestCase(false)] + [TestCase(true)] + public async Task TestBuildName_ResponseSuffixAvoidsResultCustomizationAliasCollision(bool reverseOrder) + { + var widget = InputFactory.Model("WidgetResponse"); + var gadget = InputFactory.Model("GadgetResponse"); + InputModelType[] models = reverseOrder ? [gadget, widget] : [widget, gadget]; + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: models, + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); + + CollectionAssert.AreEquivalent(new[] { "WidgetResponse", "WidgetResult" }, providers.Select(p => p.Name)); + } + [Test] public async Task TestBuildName_BackCompatTakesPrecedenceOverAcronymNormalization() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsResultCustomizationAliasCollision/WidgetResult.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsResultCustomizationAliasCollision/WidgetResult.cs new file mode 100644 index 00000000000..b67b88c2025 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsResultCustomizationAliasCollision/WidgetResult.cs @@ -0,0 +1,9 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("GadgetResult")] + public partial class WidgetResult + { + } +} From 8c8f5d2e61b3f4bf0315d7d63e5481b9923b1c50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 20:20:36 +0000 Subject: [PATCH 05/68] refactor(csharp): scope response name fallbacks to ModelProvider Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 44 +++++++++++++++++++ .../src/Providers/TypeProvider.cs | 42 +++--------------- 2 files changed, 50 insertions(+), 36 deletions(-) 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 25b395718e4..5be9fae6bb4 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 @@ -317,6 +317,50 @@ protected override string BuildName() return NormalizeTypeNameForNewContract(_inputModel.Name.ToIdentifierName()); } + private protected override TypeProvider? BuildCustomCodeView(string? generatedTypeName = null, string? generatedTypeNamespace = null) + { + var typeNamespace = generatedTypeNamespace ?? BuildNamespace(); + var typeName = generatedTypeName ?? BuildName(); + var customCodeView = base.BuildCustomCodeView(typeName, typeNamespace); + if (customCodeView is not null || _inputModel.IsExactName) + { + return customCodeView; + } + + var originalName = _inputModel.Name.ToIdentifierName(); + if (!originalName.EndsWith("Response", StringComparison.Ordinal) || + originalName == typeName || + typeName != NormalizeTypeName(originalName)) + { + return null; + } + + return base.BuildCustomCodeView(originalName, typeNamespace) ?? + base.BuildCustomCodeView(originalName.NormalizeCSharpAcronyms(), typeNamespace); + } + + private protected override TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) + { + var typeNamespace = generatedTypeNamespace ?? CustomCodeView?.Type.Namespace ?? BuildNamespace(); + var typeName = generatedTypeName ?? CustomCodeView?.Name ?? BuildName(); + var lastContractView = base.BuildLastContractView(typeName, typeNamespace); + if (lastContractView is not null || _inputModel.IsExactName) + { + return lastContractView; + } + + var originalName = _inputModel.Name.ToIdentifierName(); + if (!originalName.EndsWith("Response", StringComparison.Ordinal) || + originalName == typeName || + typeName != NormalizeTypeName(originalName)) + { + return null; + } + + return base.BuildLastContractView(originalName, typeNamespace) ?? + base.BuildLastContractView(originalName.NormalizeCSharpAcronyms(), typeNamespace); + } + protected override TypeSignatureModifiers BuildDeclarationModifiers() { var customCodeModifiers = CustomCodeView?.DeclarationModifiers ?? TypeSignatureModifiers.None; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs index 5b0857c3d5a..5dd4bfade24 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs @@ -45,36 +45,10 @@ protected TypeProvider() : this(null) } private protected virtual TypeProvider? BuildCustomCodeView(string? generatedTypeName = null, string? generatedTypeNamespace = null) - { - var typeNamespace = generatedTypeNamespace ?? BuildNamespace(); - var typeName = generatedTypeName ?? BuildName(); - var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; - var customCodeView = sourceInputModel.FindForTypeInCurrentCompilation( - typeNamespace, - typeName, + => CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( + generatedTypeNamespace ?? BuildNamespace(), + generatedTypeName ?? BuildName(), _declaringTypeName.Value); - if (customCodeView is not null || this is not ModelProvider || _inputType is null || _inputType.IsExactName) - { - return customCodeView; - } - - var originalName = _inputType.Name.ToIdentifierName(); - if (!originalName.EndsWith("Response", StringComparison.Ordinal) || - originalName == typeName || - typeName != NormalizeTypeName(originalName)) - { - return null; - } - - return sourceInputModel.FindForTypeInCurrentCompilation( - typeNamespace, - originalName, - _declaringTypeName.Value) ?? - sourceInputModel.FindForTypeInCurrentCompilation( - typeNamespace, - originalName.NormalizeCSharpAcronyms(), - _declaringTypeName.Value); - } private protected virtual TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) { @@ -90,7 +64,7 @@ protected TypeProvider() : this(null) } var originalName = _inputType.Name.ToIdentifierName(); - var normalizedOriginalName = NormalizeTypeName(originalName); + var normalizedOriginalName = originalName.NormalizeCSharpAcronyms(); if (normalizedOriginalName == originalName || typeName != normalizedOriginalName) { return null; @@ -99,11 +73,7 @@ protected TypeProvider() : this(null) return CodeModelGenerator.Instance.SourceInputModel.FindForTypeInLastContract( typeNamespace, originalName, - _declaringTypeName.Value) ?? - CodeModelGenerator.Instance.SourceInputModel.FindForTypeInLastContract( - typeNamespace, - originalName.NormalizeCSharpAcronyms(), - _declaringTypeName.Value); + _declaringTypeName.Value); } private static string? GetDeclaringTypeName(TypeProvider? declaringTypeProvider) @@ -791,7 +761,7 @@ protected string NormalizeTypeNameForNewContract(string name) return lastContractType is null ? normalizedName : name; } - private string NormalizeTypeName(string name) + private protected string NormalizeTypeName(string name) { var normalizedName = name.NormalizeCSharpAcronyms(); const string responseSuffix = "Response"; From dbba17b1ec81a0437c3eb279217d17a89d444a96 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 20:33:20 +0000 Subject: [PATCH 06/68] refactor(csharp): keep response suffix normalization in ModelProvider Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 75 ++++++++++++++++++- .../src/Providers/TypeProvider.cs | 72 +----------------- 2 files changed, 75 insertions(+), 72 deletions(-) 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 5be9fae6bb4..a3ade21f65b 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 @@ -21,6 +21,7 @@ namespace Microsoft.TypeSpec.Generator.Providers public class ModelProvider : TypeProvider { private const string AdditionalBinaryDataPropsFieldDescription = "Keeps track of any properties unknown to the library."; + private const string ResponseSuffix = "Response"; private readonly InputModelType _inputModel; // Note the description cannot be built from the constructor as it would lead to a circular dependency between the base // and derived models resulting in a stack overflow. @@ -317,6 +318,76 @@ protected override string BuildName() return NormalizeTypeNameForNewContract(_inputModel.Name.ToIdentifierName()); } + private protected override string NormalizeTypeName(string name) + { + var normalizedName = base.NormalizeTypeName(name); + if (!normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) + { + return normalizedName; + } + + var typeNamespace = BuildNamespace(); + var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + if (sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, normalizedName, DeclaringTypeName) is not null || + sourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName, DeclaringTypeName) is not null) + { + return normalizedName; + } + + var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; + var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; + // Model and enum files share a flat output directory, even across namespaces. + return inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace)) || + inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace)) || + inputNamespace.Clients.Any(HasConflictingClientName) + ? normalizedName + : resultName; + + bool HasConflictingName(InputType inputType, string inputTypeNamespace) + { + if (inputType == _inputModel) + { + return false; + } + + var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); + var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); + var customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, otherName); + if (customType is null && !inputType.IsExactName) + { + var normalizedOtherName = otherName.NormalizeCSharpAcronyms(); + customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, normalizedOtherName); + if (sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null) + { + otherName = normalizedOtherName; + if (customType is null && inputType is InputModelType && + otherName.EndsWith(ResponseSuffix, StringComparison.Ordinal) && + sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null) + { + customType = sourceInputModel.FindForTypeInCurrentCompilation( + otherNamespace, $"{otherName[..^ResponseSuffix.Length]}Result"); + } + } + } + + return string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase) || + string.Equals(customType?.Name, resultName, StringComparison.OrdinalIgnoreCase); + } + + bool HasConflictingClientName(InputClient client) + { + var clientNamespace = string.IsNullOrEmpty(client.Namespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(client.Namespace); + var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); + var customType = sourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); + return (customType?.Type.Namespace ?? clientNamespace) == typeNamespace && + (customType?.Name ?? clientName) == resultName; + } + } + private protected override TypeProvider? BuildCustomCodeView(string? generatedTypeName = null, string? generatedTypeNamespace = null) { var typeNamespace = generatedTypeNamespace ?? BuildNamespace(); @@ -328,7 +399,7 @@ protected override string BuildName() } var originalName = _inputModel.Name.ToIdentifierName(); - if (!originalName.EndsWith("Response", StringComparison.Ordinal) || + if (!originalName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || originalName == typeName || typeName != NormalizeTypeName(originalName)) { @@ -350,7 +421,7 @@ protected override string BuildName() } var originalName = _inputModel.Name.ToIdentifierName(); - if (!originalName.EndsWith("Response", StringComparison.Ordinal) || + if (!originalName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || originalName == typeName || typeName != NormalizeTypeName(originalName)) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs index 5dd4bfade24..52e2fb28618 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs @@ -24,6 +24,7 @@ public abstract class TypeProvider private Lazy _canonicalView; private Lazy _specView; private Lazy _declaringTypeName; + private protected string? DeclaringTypeName => _declaringTypeName.Value; private readonly InputType? _inputType; private readonly Dictionary _generatedPropertiesBySpecName = new(StringComparer.Ordinal); @@ -761,76 +762,7 @@ protected string NormalizeTypeNameForNewContract(string name) return lastContractType is null ? normalizedName : name; } - private protected string NormalizeTypeName(string name) - { - var normalizedName = name.NormalizeCSharpAcronyms(); - const string responseSuffix = "Response"; - if (this is not ModelProvider || !normalizedName.EndsWith(responseSuffix, StringComparison.Ordinal)) - { - return normalizedName; - } - - var typeNamespace = BuildNamespace(); - var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; - if (sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, normalizedName, _declaringTypeName.Value) is not null || - sourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName, _declaringTypeName.Value) is not null) - { - return normalizedName; - } - - var resultName = $"{normalizedName[..^responseSuffix.Length]}Result"; - var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - // Model and enum files share a flat output directory, even across namespaces. - return inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace)) || - inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace)) || - inputNamespace.Clients.Any(HasConflictingClientName) - ? normalizedName - : resultName; - - bool HasConflictingName(InputType inputType, string inputTypeNamespace) - { - if (inputType == _inputType) - { - return false; - } - - var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); - var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); - var customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, otherName); - if (customType is null && !inputType.IsExactName) - { - var normalizedOtherName = otherName.NormalizeCSharpAcronyms(); - customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, normalizedOtherName); - if (sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null) - { - otherName = normalizedOtherName; - if (customType is null && inputType is InputModelType && - otherName.EndsWith(responseSuffix, StringComparison.Ordinal) && - sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null) - { - customType = sourceInputModel.FindForTypeInCurrentCompilation( - otherNamespace, $"{otherName[..^responseSuffix.Length]}Result"); - } - } - } - - return string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase) || - string.Equals(customType?.Name, resultName, StringComparison.OrdinalIgnoreCase); - } - - bool HasConflictingClientName(InputClient client) - { - var clientNamespace = string.IsNullOrEmpty(client.Namespace) - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(client.Namespace); - var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - var customType = sourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); - return (customType?.Type.Namespace ?? clientNamespace) == typeNamespace && - (customType?.Name ?? clientName) == resultName; - } - } + private protected virtual string NormalizeTypeName(string name) => name.NormalizeCSharpAcronyms(); /// /// Resets only the cached methods so they are rebuilt on next access. From 2500c9a6305724ae90115fd401b484709a5c3338 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 20:55:34 +0000 Subject: [PATCH 07/68] refactor(csharp): reduce naming collision lookups and cover compatibility Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 97 +++++++++++-------- .../ModelProviders/ModelProviderTests.cs | 66 +++++++++++++ .../IPResponse.cs | 6 ++ .../WidgetResult.cs | 9 ++ 4 files changed, 139 insertions(+), 39 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel(LastContract)/IPResponse.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel/WidgetResult.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 a3ade21f65b..0d175bc2b33 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 @@ -337,55 +337,74 @@ private protected override string NormalizeTypeName(string name) var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; // Model and enum files share a flat output directory, even across namespaces. - return inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace)) || - inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace)) || - inputNamespace.Clients.Any(HasConflictingClientName) + return inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || + inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || + inputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)) ? normalizedName : resultName; + } - bool HasConflictingName(InputType inputType, string inputTypeNamespace) + private bool HasConflictingName(InputType inputType, string inputTypeNamespace, string resultName) + { + if (inputType == _inputModel) { - if (inputType == _inputModel) - { - return false; - } + return false; + } - var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); - var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); - var customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, otherName); - if (customType is null && !inputType.IsExactName) - { - var normalizedOtherName = otherName.NormalizeCSharpAcronyms(); - customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, normalizedOtherName); - if (sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null) - { - otherName = normalizedOtherName; - if (customType is null && inputType is InputModelType && - otherName.EndsWith(ResponseSuffix, StringComparison.Ordinal) && - sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null) - { - customType = sourceInputModel.FindForTypeInCurrentCompilation( - otherNamespace, $"{otherName[..^ResponseSuffix.Length]}Result"); - } - } - } + var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); + // Acronym normalization only changes casing, so this also covers the normalized filename. + if (string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } - return string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase) || - string.Equals(customType?.Name, resultName, StringComparison.OrdinalIgnoreCase); + var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + if (sourceInputModel.Customization is null) + { + return false; + } + + var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); + var customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, otherName); + if (customType is not null || inputType.IsExactName) + { + return string.Equals(customType?.Name, resultName, StringComparison.OrdinalIgnoreCase); } - bool HasConflictingClientName(InputClient client) + var normalizedOtherName = otherName.NormalizeCSharpAcronyms(); + if (normalizedOtherName != otherName) + { + customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, normalizedOtherName); + } + if (customType is not null) { - var clientNamespace = string.IsNullOrEmpty(client.Namespace) - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(client.Namespace); - var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - var customType = sourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); - return (customType?.Type.Namespace ?? clientNamespace) == typeNamespace && - (customType?.Name ?? clientName) == resultName; + return string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); } + + if (inputType is not InputModelType || !normalizedOtherName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) + { + return false; + } + + customType = sourceInputModel.FindForTypeInCurrentCompilation( + otherNamespace, $"{normalizedOtherName[..^ResponseSuffix.Length]}Result"); + return string.Equals(customType?.Name, resultName, StringComparison.OrdinalIgnoreCase) && + sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null && + (normalizedOtherName == otherName || + sourceInputModel.FindForTypeInLastContract(otherNamespace, normalizedOtherName) is null); + } + + private bool HasConflictingName(InputClient client, string typeNamespace, string resultName) + { + var clientNamespace = string.IsNullOrEmpty(client.Namespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(client.Namespace); + var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); + var customType = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); + return (customType?.Type.Namespace ?? clientNamespace) == typeNamespace && + (customType?.Name ?? clientName) == resultName; } private protected override TypeProvider? BuildCustomCodeView(string? generatedTypeName = null, string? generatedTypeNamespace = null) 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 b8ce1f6ab7a..de36800aef6 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,51 @@ public void TestBuildName_ResponseSuffix(string inputName, bool isExactName, str Assert.AreEqual("ServiceResponse", model.Properties[0].Name); } + [TestCase("WidgetResponse", false, "WidgetResult")] + [TestCase("WidgetResponse", true, "WidgetResponse")] + [TestCase("IpResponse", false, "IPResult")] + [TestCase("IpResponse", true, "IpResponse")] + [TestCase("widget_response", false, "WidgetResult")] + [TestCase("widget_response", true, "widget_response")] + public void TestBuildName_ResponseSuffixHonorsExactName(string inputName, bool isExactName, string expectedName) + { + var inputModel = InputFactory.Model(inputName, isExactName: isExactName); + MockHelpers.LoadMockGenerator(inputModelTypes: [inputModel]); + + var model = CodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; + + Assert.AreEqual(expectedName, model.Name); + Assert.AreEqual($"{expectedName}.cs", Path.GetFileName(model.RelativeFilePath)); + model.Reset(); + Assert.AreEqual(expectedName, model.Name); + } + + [TestCase("WidgetResponse", false, "WidgetResult")] + [TestCase("WidgetResponse", true, "WidgetResponse")] + [TestCase("IpResponse", false, "IPResult")] + [TestCase("IpResponse", true, "IPResponse")] + [TestCase("DbResponse", false, "DBResult")] + [TestCase("DbResponse", true, "DbResponse")] + public async Task TestBuildName_ResponseSuffixHonorsLastContract( + string inputName, bool hasLastContract, string expectedName) + { + var inputModel = InputFactory.Model(inputName); + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [inputModel], + lastContractCompilation: hasLastContract + ? async () => await Helpers.GetCompilationFromDirectoryAsync( + method: nameof(TestBuildName_ResponseSuffixPreservesExistingName)) + : null); + + var model = CodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; + + Assert.AreEqual(expectedName, model.Name); + Assert.AreEqual(hasLastContract, model.LastContractView is not null); + Assert.IsNull(model.CustomCodeView); + model.Reset(); + Assert.AreEqual(expectedName, model.Name); + } + [TestCase("WidgetResponse", "WidgetResponse", false, false)] [TestCase("WidgetResponse", "WidgetResponse", true, false)] [TestCase("WidgetResponse", "WidgetResponse", false, true)] @@ -277,6 +322,27 @@ await MockHelpers.LoadMockGeneratorAsync( CollectionAssert.AreEquivalent(new[] { "WidgetResponse", "WidgetResult" }, providers.Select(p => p.Name)); } + [TestCase("IpResponse", false)] + [TestCase("IpResponse", true)] + [TestCase("IPResponse", false)] + [TestCase("IPResponse", true)] + public async Task TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel( + string inputName, bool reverseOrder) + { + var widget = InputFactory.Model("WidgetResponse"); + var shipped = InputFactory.Model(inputName); + InputModelType[] models = reverseOrder ? [shipped, widget] : [widget, shipped]; + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: models, + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync( + parameters: "LastContract")); + + var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); + + CollectionAssert.AreEquivalent(new[] { "WidgetResult", "IPResponse" }, providers.Select(p => p.Name)); + } + [Test] public async Task TestBuildName_BackCompatTakesPrecedenceOverAcronymNormalization() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel(LastContract)/IPResponse.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel(LastContract)/IPResponse.cs new file mode 100644 index 00000000000..367741363b9 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel(LastContract)/IPResponse.cs @@ -0,0 +1,6 @@ +namespace Sample.Models +{ + public partial class IPResponse + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel/WidgetResult.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel/WidgetResult.cs new file mode 100644 index 00000000000..9fb6204f4f4 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel/WidgetResult.cs @@ -0,0 +1,9 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("IPResult")] + public partial class WidgetResult + { + } +} From 9d543b68f083e167cef03fdde1560585e4fe0057 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 21:37:33 +0000 Subject: [PATCH 08/68] refactor(csharp): consolidate response naming lookup helpers Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 133 +++++++++++------- .../ModelProviders/ModelProviderTests.cs | 17 ++- .../FooModel.cs | 9 ++ 3 files changed, 107 insertions(+), 52 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesCustomResponseRenameWithoutLastContract/FooModel.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 0d175bc2b33..5f15cb691bf 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 @@ -336,13 +336,16 @@ private protected override string NormalizeTypeName(string name) var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; + return HasConflictingResultName(inputNamespace, typeNamespace, resultName) + ? normalizedName + : resultName; + } + + private bool HasConflictingResultName(InputNamespace inputNamespace, string typeNamespace, string resultName) // Model and enum files share a flat output directory, even across namespaces. - return inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || + => inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || - inputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)) - ? normalizedName - : resultName; - } + inputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)); private bool HasConflictingName(InputType inputType, string inputTypeNamespace, string resultName) { @@ -367,33 +370,10 @@ private bool HasConflictingName(InputType inputType, string inputTypeNamespace, var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); - var customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, otherName); - if (customType is not null || inputType.IsExactName) - { - return string.Equals(customType?.Name, resultName, StringComparison.OrdinalIgnoreCase); - } - - var normalizedOtherName = otherName.NormalizeCSharpAcronyms(); - if (normalizedOtherName != otherName) - { - customType = sourceInputModel.FindForTypeInCurrentCompilation(otherNamespace, normalizedOtherName); - } - if (customType is not null) - { - return string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); - } - - if (inputType is not InputModelType || !normalizedOtherName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) - { - return false; - } - - customType = sourceInputModel.FindForTypeInCurrentCompilation( - otherNamespace, $"{normalizedOtherName[..^ResponseSuffix.Length]}Result"); - return string.Equals(customType?.Name, resultName, StringComparison.OrdinalIgnoreCase) && - sourceInputModel.FindForTypeInLastContract(otherNamespace, otherName) is null && - (normalizedOtherName == otherName || - sourceInputModel.FindForTypeInLastContract(otherNamespace, normalizedOtherName) is null); + var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); + return customType is { } && + string.Equals(customType.Value.Type.Name, resultName, StringComparison.OrdinalIgnoreCase) && + (!customType.Value.IsResultAlias || !HasLastContractName(otherNamespace, otherName)); } private bool HasConflictingName(InputClient client, string typeNamespace, string resultName) @@ -407,26 +387,65 @@ private bool HasConflictingName(InputClient client, string typeNamespace, string (customType?.Name ?? clientName) == resultName; } - private protected override TypeProvider? BuildCustomCodeView(string? generatedTypeName = null, string? generatedTypeNamespace = null) + private (TypeProvider Type, bool IsResultAlias)? FindCustomizationType( + string typeNamespace, + IEnumerable<(string Name, bool IsResultAlias)> lookupNames) { - var typeNamespace = generatedTypeNamespace ?? BuildNamespace(); - var typeName = generatedTypeName ?? BuildName(); - var customCodeView = base.BuildCustomCodeView(typeName, typeNamespace); - if (customCodeView is not null || _inputModel.IsExactName) + var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + foreach (var lookupName in lookupNames) { - return customCodeView; + var customType = sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, lookupName.Name); + if (customType is not null) + { + return (customType, lookupName.IsResultAlias); + } } - var originalName = _inputModel.Name.ToIdentifierName(); - if (!originalName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || - originalName == typeName || - typeName != NormalizeTypeName(originalName)) + return null; + } + + private static IEnumerable<(string Name, bool IsResultAlias)> GetCustomizationLookupNames(InputType inputType, string name) + { + yield return (name, false); + if (inputType.IsExactName) { - return null; + yield break; } - return base.BuildCustomCodeView(originalName, typeNamespace) ?? - base.BuildCustomCodeView(originalName.NormalizeCSharpAcronyms(), typeNamespace); + var normalizedName = name.NormalizeCSharpAcronyms(); + if (normalizedName != name) + { + yield return (normalizedName, false); + } + + if (inputType is InputModelType && normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) + { + yield return ($"{normalizedName[..^ResponseSuffix.Length]}Result", true); + } + } + + private bool HasLastContractName(string typeNamespace, string name) + { + var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + if (sourceInputModel.FindForTypeInLastContract(typeNamespace, name) is not null) + { + return true; + } + + var normalizedName = name.NormalizeCSharpAcronyms(); + return normalizedName != name && + sourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName) is not null; + } + + private protected override TypeProvider? BuildCustomCodeView(string? generatedTypeName = null, string? generatedTypeNamespace = null) + { + var typeNamespace = generatedTypeNamespace ?? BuildNamespace(); + var typeName = generatedTypeName ?? BuildName(); + var customCodeView = base.BuildCustomCodeView(typeName, typeNamespace); + return customCodeView ?? BuildResponseSuffixFallbackView( + typeName, + typeNamespace, + (name, ns) => base.BuildCustomCodeView(name, ns)); } private protected override TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) @@ -434,21 +453,33 @@ private bool HasConflictingName(InputClient client, string typeNamespace, string var typeNamespace = generatedTypeNamespace ?? CustomCodeView?.Type.Namespace ?? BuildNamespace(); var typeName = generatedTypeName ?? CustomCodeView?.Name ?? BuildName(); var lastContractView = base.BuildLastContractView(typeName, typeNamespace); - if (lastContractView is not null || _inputModel.IsExactName) + return lastContractView ?? BuildResponseSuffixFallbackView( + typeName, + typeNamespace, + (name, ns) => base.BuildLastContractView(name, ns)); + } + + private TypeProvider? BuildResponseSuffixFallbackView( + string typeName, + string typeNamespace, + Func buildView) + { + if (_inputModel.IsExactName) { - return lastContractView; + return null; } var originalName = _inputModel.Name.ToIdentifierName(); - if (!originalName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || + var normalizedOriginalName = originalName.NormalizeCSharpAcronyms(); + if (!normalizedOriginalName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || originalName == typeName || - typeName != NormalizeTypeName(originalName)) + typeName != $"{normalizedOriginalName[..^ResponseSuffix.Length]}Result") { return null; } - return base.BuildLastContractView(originalName, typeNamespace) ?? - base.BuildLastContractView(originalName.NormalizeCSharpAcronyms(), typeNamespace); + return buildView(originalName, typeNamespace) ?? + (normalizedOriginalName == originalName ? null : buildView(normalizedOriginalName, typeNamespace)); } protected override TypeSignatureModifiers BuildDeclarationModifiers() 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 de36800aef6..baa32bcb710 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 @@ -249,6 +249,21 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.IsNotNull(model.CustomCodeView); } + [Test] + public async Task TestBuildName_ResponseSuffixPreservesCustomResponseRenameWithoutLastContract() + { + var inputModel = InputFactory.Model("FooResponse"); + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [inputModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var model = CodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; + + Assert.AreEqual("FooModel", model.Name); + Assert.IsNotNull(model.CustomCodeView); + Assert.IsNull(model.LastContractView); + } + [TestCase(false, false, "WidgetResult", false)] [TestCase(false, true, "WidgetResult", false)] [TestCase(true, false, "WidgetResult", false)] @@ -3767,7 +3782,7 @@ public void TestBuildRawDataField_BasedOnUsage(InputModelTypeUsage usage, bool s [TearDown] public void CleanUp() { - if(_projectDir != null && Directory.Exists(_projectDir)) + if (_projectDir != null && Directory.Exists(_projectDir)) { Directory.Delete(_projectDir, true); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesCustomResponseRenameWithoutLastContract/FooModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesCustomResponseRenameWithoutLastContract/FooModel.cs new file mode 100644 index 00000000000..919cf266b9f --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesCustomResponseRenameWithoutLastContract/FooModel.cs @@ -0,0 +1,9 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("FooResponse")] + public partial class FooModel + { + } +} From 6584968190663377690849dac8e34099ca6e99ce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 18 Sep 2026 21:46:31 +0000 Subject: [PATCH 09/68] fix(csharp): honor customized result model names Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 55 +++++++++++++++---- .../ModelProviders/ModelProviderTests.cs | 13 +++++ .../CustomWidgetResult.cs | 9 +++ 3 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAllowsCustomizedModelResultName/CustomWidgetResult.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 5f15cb691bf..1c00d3d1c40 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 @@ -355,24 +355,24 @@ private bool HasConflictingName(InputType inputType, string inputTypeNamespace, } var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); - // Acronym normalization only changes casing, so this also covers the normalized filename. - if (string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + // Acronym normalization only changes casing, so this also covers the normalized filename. + var hasMatchingInputName = string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase); if (sourceInputModel.Customization is null) { - return false; + return hasMatchingInputName; } var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); - return customType is { } && - string.Equals(customType.Value.Type.Name, resultName, StringComparison.OrdinalIgnoreCase) && + if (customType is null) + { + return hasMatchingInputName; + } + + return string.Equals(customType.Value.Type.Name, resultName, StringComparison.OrdinalIgnoreCase) && (!customType.Value.IsResultAlias || !HasLastContractName(otherNamespace, otherName)); } @@ -441,13 +441,48 @@ private bool HasLastContractName(string typeNamespace, string name) { var typeNamespace = generatedTypeNamespace ?? BuildNamespace(); var typeName = generatedTypeName ?? BuildName(); - var customCodeView = base.BuildCustomCodeView(typeName, typeNamespace); + var customCodeView = HasCustomizedSiblingInputName(typeName) + ? null + : base.BuildCustomCodeView(typeName, typeNamespace); return customCodeView ?? BuildResponseSuffixFallbackView( typeName, typeNamespace, (name, ns) => base.BuildCustomCodeView(name, ns)); } + private bool HasCustomizedSiblingInputName(string typeName) + { + if (CodeModelGenerator.Instance.SourceInputModel.Customization is null) + { + return false; + } + + var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; + return inputNamespace.Models.Any(model => HasCustomizedSiblingInputName(model, model.Namespace, typeName)) || + inputNamespace.Enums.Any(@enum => HasCustomizedSiblingInputName(@enum, @enum.Namespace, typeName)); + } + + private bool HasCustomizedSiblingInputName(InputType inputType, string inputTypeNamespace, string typeName) + { + if (inputType == _inputModel) + { + return false; + } + + var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); + if (!string.Equals(otherName, typeName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); + var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); + return customType is not null && + !string.Equals(customType.Value.Type.Name, typeName, StringComparison.OrdinalIgnoreCase); + } + private protected override TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) { var typeNamespace = generatedTypeNamespace ?? CustomCodeView?.Type.Namespace ?? BuildNamespace(); 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 baa32bcb710..98490dc7204 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 @@ -321,6 +321,19 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(other)!.Name); } + [Test] + public async Task TestBuildName_ResponseSuffixAllowsCustomizedModelResultName() + { + var response = InputFactory.Model("WidgetResponse"); + var result = InputFactory.Model("WidgetResult"); + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [response, result], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + Assert.AreEqual("CustomWidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(result)!.Name); + } + [TestCase(false)] [TestCase(true)] public async Task TestBuildName_ResponseSuffixAvoidsResultCustomizationAliasCollision(bool reverseOrder) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAllowsCustomizedModelResultName/CustomWidgetResult.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAllowsCustomizedModelResultName/CustomWidgetResult.cs new file mode 100644 index 00000000000..df1a4cdeee7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAllowsCustomizedModelResultName/CustomWidgetResult.cs @@ -0,0 +1,9 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("WidgetResult")] + public partial class CustomWidgetResult + { + } +} From 14a355607f26a60d0911ef9cf651cf6e30942bac Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:00:26 +0000 Subject: [PATCH 10/68] perf(csharp): cache model naming lookups Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 289 +++++++++++------- .../src/TypeFactory.cs | 3 + 2 files changed, 183 insertions(+), 109 deletions(-) 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 1c00d3d1c40..85d00cd6812 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 @@ -335,107 +335,40 @@ private protected override string NormalizeTypeName(string name) } var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; - var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - return HasConflictingResultName(inputNamespace, typeNamespace, resultName) + return HasConflictingResultName(typeNamespace, resultName) ? normalizedName : resultName; } - private bool HasConflictingResultName(InputNamespace inputNamespace, string typeNamespace, string resultName) - // Model and enum files share a flat output directory, even across namespaces. - => inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || - inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || - inputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)); - - private bool HasConflictingName(InputType inputType, string inputTypeNamespace, string resultName) - { - if (inputType == _inputModel) - { - return false; - } - - var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); - var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; - // Acronym normalization only changes casing, so this also covers the normalized filename. - var hasMatchingInputName = string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase); - if (sourceInputModel.Customization is null) - { - return hasMatchingInputName; - } - - var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); - var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); - if (customType is null) - { - return hasMatchingInputName; - } - - return string.Equals(customType.Value.Type.Name, resultName, StringComparison.OrdinalIgnoreCase) && - (!customType.Value.IsResultAlias || !HasLastContractName(otherNamespace, otherName)); - } - - private bool HasConflictingName(InputClient client, string typeNamespace, string resultName) - { - var clientNamespace = string.IsNullOrEmpty(client.Namespace) - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(client.Namespace); - var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - var customType = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); - return (customType?.Type.Namespace ?? clientNamespace) == typeNamespace && - (customType?.Name ?? clientName) == resultName; - } - - private (TypeProvider Type, bool IsResultAlias)? FindCustomizationType( - string typeNamespace, - IEnumerable<(string Name, bool IsResultAlias)> lookupNames) + private bool HasConflictingResultName(string typeNamespace, string resultName) { - var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; - foreach (var lookupName in lookupNames) - { - var customType = sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, lookupName.Name); - if (customType is not null) - { - return (customType, lookupName.IsResultAlias); - } - } - - return null; + var nameCache = CodeModelGenerator.Instance.TypeFactory.ModelProviderNameCache; + // Model and enum files share a flat output directory, even across namespaces. + return nameCache.FindModels(resultName).Any(entry => HasConflictingName(entry, resultName)) || + nameCache.FindEnums(resultName).Any(entry => HasConflictingName(entry, resultName)) || + nameCache.FindClients(resultName).Any(entry => HasConflictingName(entry, typeNamespace, resultName)); } - private static IEnumerable<(string Name, bool IsResultAlias)> GetCustomizationLookupNames(InputType inputType, string name) + private bool HasConflictingName(ModelProviderNameCache.Entry entry, string resultName) { - yield return (name, false); - if (inputType.IsExactName) + if (entry.InputType == _inputModel) { - yield break; + return false; } - var normalizedName = name.NormalizeCSharpAcronyms(); - if (normalizedName != name) + if (entry.CustomType is null) { - yield return (normalizedName, false); + // Acronym normalization only changes casing, so this also covers the normalized filename. + return string.Equals(entry.InputName, resultName, StringComparison.OrdinalIgnoreCase); } - if (inputType is InputModelType && normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) - { - yield return ($"{normalizedName[..^ResponseSuffix.Length]}Result", true); - } + return string.Equals(entry.CustomType.Name, resultName, StringComparison.OrdinalIgnoreCase) && + (!entry.IsResultAlias || !entry.HasLastContractName); } - private bool HasLastContractName(string typeNamespace, string name) - { - var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; - if (sourceInputModel.FindForTypeInLastContract(typeNamespace, name) is not null) - { - return true; - } - - var normalizedName = name.NormalizeCSharpAcronyms(); - return normalizedName != name && - sourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName) is not null; - } + private static bool HasConflictingName(ModelProviderNameCache.Entry entry, string typeNamespace, string resultName) + => (entry.CustomType?.Type.Namespace ?? entry.Namespace) == typeNamespace && + (entry.CustomType?.Name ?? entry.InputName) == resultName; private protected override TypeProvider? BuildCustomCodeView(string? generatedTypeName = null, string? generatedTypeNamespace = null) { @@ -457,30 +390,12 @@ private bool HasCustomizedSiblingInputName(string typeName) return false; } - var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - return inputNamespace.Models.Any(model => HasCustomizedSiblingInputName(model, model.Namespace, typeName)) || - inputNamespace.Enums.Any(@enum => HasCustomizedSiblingInputName(@enum, @enum.Namespace, typeName)); - } - - private bool HasCustomizedSiblingInputName(InputType inputType, string inputTypeNamespace, string typeName) - { - if (inputType == _inputModel) - { - return false; - } - - var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); - if (!string.Equals(otherName, typeName, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); - var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); - return customType is not null && - !string.Equals(customType.Value.Type.Name, typeName, StringComparison.OrdinalIgnoreCase); + var nameCache = CodeModelGenerator.Instance.TypeFactory.ModelProviderNameCache; + return nameCache.FindModels(typeName).Concat(nameCache.FindEnums(typeName)).Any(entry => + entry.InputType != _inputModel && + string.Equals(entry.InputName, typeName, StringComparison.OrdinalIgnoreCase) && + entry.CustomType is not null && + !string.Equals(entry.CustomType.Name, typeName, StringComparison.OrdinalIgnoreCase)); } private protected override TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) @@ -2048,5 +1963,161 @@ private static string BuildAdditionalTypePropertiesFieldName(CSharpType addition return $"_additional{name.ToIdentifierName()}Properties"; } + + internal sealed class ModelProviderNameCache + { + private readonly CodeModelGenerator _generator; + private readonly Lazy>> _models; + private readonly Lazy>> _enums; + private readonly Lazy>> _clients; + + internal ModelProviderNameCache(CodeModelGenerator generator) + { + _generator = generator; + _models = new(() => BuildTypeMap( + generator.InputLibrary.InputNamespace.Models, + model => model.Namespace)); + _enums = new(() => BuildTypeMap( + generator.InputLibrary.InputNamespace.Enums, + @enum => @enum.Namespace)); + _clients = new(BuildClientMap); + } + + internal IReadOnlyList FindModels(string name) => Find(_models.Value, name); + internal IReadOnlyList FindEnums(string name) => Find(_enums.Value, name); + internal IReadOnlyList FindClients(string name) => Find(_clients.Value, name); + + private static IReadOnlyList Find(Dictionary> cache, string name) + => cache.TryGetValue(name, out var entries) ? entries : []; + + private Dictionary> BuildTypeMap( + IEnumerable inputTypes, + Func getNamespace) + where T : InputType + { + var cache = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var inputType in inputTypes) + { + var inputName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); + var typeNamespace = GetNamespace(getNamespace(inputType)); + var customType = FindCustomizationType(typeNamespace, GetCustomizationLookupNames(inputType, inputName)); + var entry = new Entry( + inputType, + typeNamespace, + inputName, + customType?.Type, + customType?.IsResultAlias == true, + customType?.IsResultAlias == true && HasLastContractName(typeNamespace, inputName)); + Add(cache, inputName, entry); + if (customType is not null && + !string.Equals(customType.Value.Type.Name, inputName, StringComparison.OrdinalIgnoreCase)) + { + Add(cache, customType.Value.Type.Name, entry); + } + } + + return cache; + } + + private Dictionary> BuildClientMap() + { + var cache = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var client in _generator.InputLibrary.InputNamespace.Clients) + { + var inputName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); + var typeNamespace = GetNamespace(client.Namespace); + var customType = _generator.SourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, inputName); + var entry = new Entry(client, typeNamespace, inputName, customType, false, false); + Add(cache, inputName, entry); + if (customType is not null && + !string.Equals(customType.Name, inputName, StringComparison.OrdinalIgnoreCase)) + { + Add(cache, customType.Name, entry); + } + } + + return cache; + } + + private string GetNamespace(string inputNamespace) + => string.IsNullOrEmpty(inputNamespace) + ? _generator.TypeFactory.PrimaryNamespace + : _generator.TypeFactory.GetCleanNameSpace(inputNamespace); + + private (TypeProvider Type, bool IsResultAlias)? FindCustomizationType( + string typeNamespace, + IEnumerable<(string Name, bool IsResultAlias)> lookupNames) + { + foreach (var lookupName in lookupNames) + { + var customType = _generator.SourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, lookupName.Name); + if (customType is not null) + { + return (customType, lookupName.IsResultAlias); + } + } + + return null; + } + + private static IEnumerable<(string Name, bool IsResultAlias)> GetCustomizationLookupNames(InputType inputType, string name) + { + yield return (name, false); + if (inputType.IsExactName) + { + yield break; + } + + var normalizedName = name.NormalizeCSharpAcronyms(); + if (normalizedName != name) + { + yield return (normalizedName, false); + } + + if (inputType is InputModelType && normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) + { + yield return ($"{normalizedName[..^ResponseSuffix.Length]}Result", true); + } + } + + private bool HasLastContractName(string typeNamespace, string name) + { + if (_generator.SourceInputModel.FindForTypeInLastContract(typeNamespace, name) is not null) + { + return true; + } + + var normalizedName = name.NormalizeCSharpAcronyms(); + return normalizedName != name && + _generator.SourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName) is not null; + } + + private static void Add(Dictionary> cache, string name, Entry entry) + { + if (!cache.TryGetValue(name, out var entries)) + { + entries = []; + cache.Add(name, entries); + } + + entries.Add(entry); + } + + internal sealed class Entry( + object inputType, + string @namespace, + string inputName, + TypeProvider? customType, + bool isResultAlias, + bool hasLastContractName) + { + internal object InputType { get; } = inputType; + internal string Namespace { get; } = @namespace; + internal string InputName { get; } = inputName; + internal TypeProvider? CustomType { get; } = customType; + internal bool IsResultAlias { get; } = isResultAlias; + internal bool HasLastContractName { get; } = hasLastContractName; + } + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index 17dcbd3808f..8ad05b3b7c7 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -30,6 +30,9 @@ private ChangeTrackingDictionaryDefinition ChangeTrackingDictionaryProvider private Dictionary InputTypeToModelProvider { get; } = []; + private ModelProvider.ModelProviderNameCache? _modelProviderNameCache; + internal ModelProvider.ModelProviderNameCache ModelProviderNameCache => _modelProviderNameCache ??= new(CodeModelGenerator.Instance); + public IDictionary CSharpTypeMap { get; } = new Dictionary(CSharpType.IgnoreNullableComparer); // Maps C# type names to TypeProviders for efficient lookup when resolving types by name From 5cddfaea291871dbb8cdd6c6a269b8a7465cc152 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:02:21 +0000 Subject: [PATCH 11/68] refactor(csharp): type model name cache entries Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 55 ++++++++++++------- .../src/TypeFactory.cs | 4 +- 2 files changed, 36 insertions(+), 23 deletions(-) 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 85d00cd6812..21d1e46ccf1 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 @@ -349,7 +349,7 @@ private bool HasConflictingResultName(string typeNamespace, string resultName) nameCache.FindClients(resultName).Any(entry => HasConflictingName(entry, typeNamespace, resultName)); } - private bool HasConflictingName(ModelProviderNameCache.Entry entry, string resultName) + private bool HasConflictingName(NameCache.TypeEntry entry, string resultName) { if (entry.InputType == _inputModel) { @@ -366,7 +366,7 @@ private bool HasConflictingName(ModelProviderNameCache.Entry entry, string resul (!entry.IsResultAlias || !entry.HasLastContractName); } - private static bool HasConflictingName(ModelProviderNameCache.Entry entry, string typeNamespace, string resultName) + private static bool HasConflictingName(NameCache.ClientEntry entry, string typeNamespace, string resultName) => (entry.CustomType?.Type.Namespace ?? entry.Namespace) == typeNamespace && (entry.CustomType?.Name ?? entry.InputName) == resultName; @@ -1964,14 +1964,17 @@ private static string BuildAdditionalTypePropertiesFieldName(CSharpType addition return $"_additional{name.ToIdentifierName()}Properties"; } - internal sealed class ModelProviderNameCache + /// + /// Indexes input and customized type names once per generator for collision lookups. + /// + internal sealed class NameCache { private readonly CodeModelGenerator _generator; - private readonly Lazy>> _models; - private readonly Lazy>> _enums; - private readonly Lazy>> _clients; + private readonly Lazy>> _models; + private readonly Lazy>> _enums; + private readonly Lazy>> _clients; - internal ModelProviderNameCache(CodeModelGenerator generator) + internal NameCache(CodeModelGenerator generator) { _generator = generator; _models = new(() => BuildTypeMap( @@ -1983,25 +1986,25 @@ internal ModelProviderNameCache(CodeModelGenerator generator) _clients = new(BuildClientMap); } - internal IReadOnlyList FindModels(string name) => Find(_models.Value, name); - internal IReadOnlyList FindEnums(string name) => Find(_enums.Value, name); - internal IReadOnlyList FindClients(string name) => Find(_clients.Value, name); + internal IReadOnlyList FindModels(string name) => Find(_models.Value, name); + internal IReadOnlyList FindEnums(string name) => Find(_enums.Value, name); + internal IReadOnlyList FindClients(string name) => Find(_clients.Value, name); - private static IReadOnlyList Find(Dictionary> cache, string name) + private static IReadOnlyList Find(Dictionary> cache, string name) => cache.TryGetValue(name, out var entries) ? entries : []; - private Dictionary> BuildTypeMap( + private Dictionary> BuildTypeMap( IEnumerable inputTypes, Func getNamespace) where T : InputType { - var cache = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var cache = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (var inputType in inputTypes) { var inputName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); var typeNamespace = GetNamespace(getNamespace(inputType)); var customType = FindCustomizationType(typeNamespace, GetCustomizationLookupNames(inputType, inputName)); - var entry = new Entry( + var entry = new TypeEntry( inputType, typeNamespace, inputName, @@ -2019,15 +2022,15 @@ private Dictionary> BuildTypeMap( return cache; } - private Dictionary> BuildClientMap() + private Dictionary> BuildClientMap() { - var cache = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var cache = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (var client in _generator.InputLibrary.InputNamespace.Clients) { var inputName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); var typeNamespace = GetNamespace(client.Namespace); var customType = _generator.SourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, inputName); - var entry = new Entry(client, typeNamespace, inputName, customType, false, false); + var entry = new ClientEntry(typeNamespace, inputName, customType); Add(cache, inputName, entry); if (customType is not null && !string.Equals(customType.Name, inputName, StringComparison.OrdinalIgnoreCase)) @@ -2092,7 +2095,7 @@ private bool HasLastContractName(string typeNamespace, string name) _generator.SourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName) is not null; } - private static void Add(Dictionary> cache, string name, Entry entry) + private static void Add(Dictionary> cache, string name, T entry) { if (!cache.TryGetValue(name, out var entries)) { @@ -2103,21 +2106,31 @@ private static void Add(Dictionary> cache, string name, Entr entries.Add(entry); } - internal sealed class Entry( - object inputType, + internal sealed class TypeEntry( + InputType inputType, string @namespace, string inputName, TypeProvider? customType, bool isResultAlias, bool hasLastContractName) { - internal object InputType { get; } = inputType; + internal InputType InputType { get; } = inputType; internal string Namespace { get; } = @namespace; internal string InputName { get; } = inputName; internal TypeProvider? CustomType { get; } = customType; internal bool IsResultAlias { get; } = isResultAlias; internal bool HasLastContractName { get; } = hasLastContractName; } + + internal sealed class ClientEntry( + string @namespace, + string inputName, + TypeProvider? customType) + { + internal string Namespace { get; } = @namespace; + internal string InputName { get; } = inputName; + internal TypeProvider? CustomType { get; } = customType; + } } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index 8ad05b3b7c7..2ab6e417c43 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -30,8 +30,8 @@ private ChangeTrackingDictionaryDefinition ChangeTrackingDictionaryProvider private Dictionary InputTypeToModelProvider { get; } = []; - private ModelProvider.ModelProviderNameCache? _modelProviderNameCache; - internal ModelProvider.ModelProviderNameCache ModelProviderNameCache => _modelProviderNameCache ??= new(CodeModelGenerator.Instance); + private ModelProvider.NameCache? _modelProviderNameCache; + internal ModelProvider.NameCache ModelProviderNameCache => _modelProviderNameCache ??= new(CodeModelGenerator.Instance); public IDictionary CSharpTypeMap { get; } = new Dictionary(CSharpType.IgnoreNullableComparer); From 6200dd5e413fd35c50a84cbd66a9d0bd23c41c88 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:03:40 +0000 Subject: [PATCH 12/68] refactor(csharp): make naming cache initialization lazy Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 3 --- .../generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) 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 21d1e46ccf1..74bc4768b1d 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 @@ -2006,7 +2006,6 @@ private Dictionary> BuildTypeMap( var customType = FindCustomizationType(typeNamespace, GetCustomizationLookupNames(inputType, inputName)); var entry = new TypeEntry( inputType, - typeNamespace, inputName, customType?.Type, customType?.IsResultAlias == true, @@ -2108,14 +2107,12 @@ private static void Add(Dictionary> cache, string name, T ent internal sealed class TypeEntry( InputType inputType, - string @namespace, string inputName, TypeProvider? customType, bool isResultAlias, bool hasLastContractName) { internal InputType InputType { get; } = inputType; - internal string Namespace { get; } = @namespace; internal string InputName { get; } = inputName; internal TypeProvider? CustomType { get; } = customType; internal bool IsResultAlias { get; } = isResultAlias; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index 2ab6e417c43..45f62951ee4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -30,8 +30,8 @@ private ChangeTrackingDictionaryDefinition ChangeTrackingDictionaryProvider private Dictionary InputTypeToModelProvider { get; } = []; - private ModelProvider.NameCache? _modelProviderNameCache; - internal ModelProvider.NameCache ModelProviderNameCache => _modelProviderNameCache ??= new(CodeModelGenerator.Instance); + private readonly Lazy _modelProviderNameCache = new(() => new(CodeModelGenerator.Instance)); + internal ModelProvider.NameCache ModelProviderNameCache => _modelProviderNameCache.Value; public IDictionary CSharpTypeMap { get; } = new Dictionary(CSharpType.IgnoreNullableComparer); From b84a31957e3c9408152660139ff8628b18dc670c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:04:57 +0000 Subject: [PATCH 13/68] refactor(csharp): clarify naming cache invariants Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) 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 74bc4768b1d..6119712afb2 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 @@ -363,7 +363,7 @@ private bool HasConflictingName(NameCache.TypeEntry entry, string resultName) } return string.Equals(entry.CustomType.Name, resultName, StringComparison.OrdinalIgnoreCase) && - (!entry.IsResultAlias || !entry.HasLastContractName); + (!entry.IsResultAlias || !entry.HasLastContractAliasName); } private static bool HasConflictingName(NameCache.ClientEntry entry, string typeNamespace, string resultName) @@ -2004,17 +2004,18 @@ private Dictionary> BuildTypeMap( var inputName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); var typeNamespace = GetNamespace(getNamespace(inputType)); var customType = FindCustomizationType(typeNamespace, GetCustomizationLookupNames(inputType, inputName)); + var customTypeProvider = customType?.Type; var entry = new TypeEntry( inputType, inputName, - customType?.Type, + customTypeProvider, customType?.IsResultAlias == true, customType?.IsResultAlias == true && HasLastContractName(typeNamespace, inputName)); Add(cache, inputName, entry); - if (customType is not null && - !string.Equals(customType.Value.Type.Name, inputName, StringComparison.OrdinalIgnoreCase)) + if (customTypeProvider is not null && + !string.Equals(customTypeProvider.Name, inputName, StringComparison.OrdinalIgnoreCase)) { - Add(cache, customType.Value.Type.Name, entry); + Add(cache, customTypeProvider.Name, entry); } } @@ -2110,13 +2111,13 @@ internal sealed class TypeEntry( string inputName, TypeProvider? customType, bool isResultAlias, - bool hasLastContractName) + bool hasLastContractAliasName) { internal InputType InputType { get; } = inputType; internal string InputName { get; } = inputName; internal TypeProvider? CustomType { get; } = customType; internal bool IsResultAlias { get; } = isResultAlias; - internal bool HasLastContractName { get; } = hasLastContractName; + internal bool HasLastContractAliasName { get; } = hasLastContractAliasName; } internal sealed class ClientEntry( From 6287c0cebc10ff5ff41bfe3cdab0eda33ca2288e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:06:16 +0000 Subject: [PATCH 14/68] refactor(csharp): freeze naming cache entries Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) 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 6119712afb2..af0cf077247 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 @@ -1966,13 +1966,14 @@ private static string BuildAdditionalTypePropertiesFieldName(CSharpType addition /// /// Indexes input and customized type names once per generator for collision lookups. + /// The input namespace and customization compilations must be finalized before the first lookup. /// internal sealed class NameCache { private readonly CodeModelGenerator _generator; - private readonly Lazy>> _models; - private readonly Lazy>> _enums; - private readonly Lazy>> _clients; + private readonly Lazy>> _models; + private readonly Lazy>> _enums; + private readonly Lazy>> _clients; internal NameCache(CodeModelGenerator generator) { @@ -1990,10 +1991,10 @@ internal NameCache(CodeModelGenerator generator) internal IReadOnlyList FindEnums(string name) => Find(_enums.Value, name); internal IReadOnlyList FindClients(string name) => Find(_clients.Value, name); - private static IReadOnlyList Find(Dictionary> cache, string name) + private static IReadOnlyList Find(IReadOnlyDictionary> cache, string name) => cache.TryGetValue(name, out var entries) ? entries : []; - private Dictionary> BuildTypeMap( + private IReadOnlyDictionary> BuildTypeMap( IEnumerable inputTypes, Func getNamespace) where T : InputType @@ -2019,10 +2020,10 @@ private Dictionary> BuildTypeMap( } } - return cache; + return Freeze(cache); } - private Dictionary> BuildClientMap() + private IReadOnlyDictionary> BuildClientMap() { var cache = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (var client in _generator.InputLibrary.InputNamespace.Clients) @@ -2039,7 +2040,7 @@ private Dictionary> BuildClientMap() } } - return cache; + return Freeze(cache); } private string GetNamespace(string inputNamespace) @@ -2106,6 +2107,12 @@ private static void Add(Dictionary> cache, string name, T ent entries.Add(entry); } + private static IReadOnlyDictionary> Freeze(Dictionary> cache) + => cache.ToDictionary( + pair => pair.Key, + pair => (IReadOnlyList)pair.Value.ToArray(), + StringComparer.OrdinalIgnoreCase); + internal sealed class TypeEntry( InputType inputType, string inputName, From fd3e2b6e656817ef35149a52202e4b8cc8b7d2a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:07:37 +0000 Subject: [PATCH 15/68] refactor(csharp): simplify cached alias state Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) 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 af0cf077247..5c41517fc65 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 @@ -363,7 +363,7 @@ private bool HasConflictingName(NameCache.TypeEntry entry, string resultName) } return string.Equals(entry.CustomType.Name, resultName, StringComparison.OrdinalIgnoreCase) && - (!entry.IsResultAlias || !entry.HasLastContractAliasName); + !entry.HasLastContractAliasName; } private static bool HasConflictingName(NameCache.ClientEntry entry, string typeNamespace, string resultName) @@ -2010,7 +2010,6 @@ private IReadOnlyDictionary> BuildTypeMap( inputType, inputName, customTypeProvider, - customType?.IsResultAlias == true, customType?.IsResultAlias == true && HasLastContractName(typeNamespace, inputName)); Add(cache, inputName, entry); if (customTypeProvider is not null && @@ -2117,13 +2116,26 @@ internal sealed class TypeEntry( InputType inputType, string inputName, TypeProvider? customType, - bool isResultAlias, bool hasLastContractAliasName) { + /// + /// Gets the input type represented by this entry. + /// internal InputType InputType { get; } = inputType; + + /// + /// Gets the input name after identifier normalization, unless the name is exact. + /// internal string InputName { get; } = inputName; + + /// + /// Gets the customization resolved from input, acronym, or result-alias lookup names. + /// internal TypeProvider? CustomType { get; } = customType; - internal bool IsResultAlias { get; } = isResultAlias; + + /// + /// Gets whether a result-alias customization maps to an input name in the last contract. + /// internal bool HasLastContractAliasName { get; } = hasLastContractAliasName; } @@ -2132,8 +2144,19 @@ internal sealed class ClientEntry( string inputName, TypeProvider? customType) { + /// + /// Gets the cleaned client namespace. + /// internal string Namespace { get; } = @namespace; + + /// + /// Gets the client name after identifier normalization, unless the name is exact. + /// internal string InputName { get; } = inputName; + + /// + /// Gets the customization resolved from the client input name. + /// internal TypeProvider? CustomType { get; } = customType; } } From c3558a0d764bf584222671b961edf1697626472f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:09:30 +0000 Subject: [PATCH 16/68] refactor(csharp): bind name cache to generator Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/CodeModelGenerator.cs | 4 +++ .../src/Providers/ModelProvider.cs | 31 ++++++++++--------- .../src/TypeFactory.cs | 3 -- 3 files changed, 20 insertions(+), 18 deletions(-) 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..057e7e5d7ae 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 @@ -27,6 +27,7 @@ public abstract class CodeModelGenerator private List _visitors = []; private List _additionalMetadataReferences = []; private readonly Dictionary> _customCodeMethodDependencies = new(StringComparer.Ordinal); + private readonly Lazy _modelProviderNameCache; private static CodeModelGenerator? _instance; private List _sharedSourceDirectories = []; public const string GeneratorMetadataName = "GeneratorName"; @@ -63,6 +64,7 @@ public CodeModelGenerator(GeneratorContext context) _inputLibrary = new InputLibrary(Configuration.OutputDirectory); TypeFactory = new TypeFactory(); Emitter = new Emitter(Console.OpenStandardOutput()); + _modelProviderNameCache = new(() => new(this)); } // for mocking @@ -70,6 +72,7 @@ public CodeModelGenerator(GeneratorContext context) protected CodeModelGenerator() #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { + _modelProviderNameCache = new(() => new(this)); } internal bool IsNewProject { get; set; } @@ -80,6 +83,7 @@ protected CodeModelGenerator() // Extensibility points to be implemented by a generator public virtual TypeFactory TypeFactory { get; } + internal ModelProvider.NameCache ModelProviderNameCache => _modelProviderNameCache.Value; private SourceInputModel? _sourceInputModel; private List _rewriters = []; 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 5c41517fc65..cf44b862463 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 @@ -342,7 +342,7 @@ private protected override string NormalizeTypeName(string name) private bool HasConflictingResultName(string typeNamespace, string resultName) { - var nameCache = CodeModelGenerator.Instance.TypeFactory.ModelProviderNameCache; + var nameCache = CodeModelGenerator.Instance.ModelProviderNameCache; // Model and enum files share a flat output directory, even across namespaces. return nameCache.FindModels(resultName).Any(entry => HasConflictingName(entry, resultName)) || nameCache.FindEnums(resultName).Any(entry => HasConflictingName(entry, resultName)) || @@ -356,13 +356,13 @@ private bool HasConflictingName(NameCache.TypeEntry entry, string resultName) return false; } - if (entry.CustomType is null) + if (entry.CustomTypeName is null) { // Acronym normalization only changes casing, so this also covers the normalized filename. return string.Equals(entry.InputName, resultName, StringComparison.OrdinalIgnoreCase); } - return string.Equals(entry.CustomType.Name, resultName, StringComparison.OrdinalIgnoreCase) && + return string.Equals(entry.CustomTypeName, resultName, StringComparison.OrdinalIgnoreCase) && !entry.HasLastContractAliasName; } @@ -390,12 +390,12 @@ private bool HasCustomizedSiblingInputName(string typeName) return false; } - var nameCache = CodeModelGenerator.Instance.TypeFactory.ModelProviderNameCache; + var nameCache = CodeModelGenerator.Instance.ModelProviderNameCache; return nameCache.FindModels(typeName).Concat(nameCache.FindEnums(typeName)).Any(entry => entry.InputType != _inputModel && string.Equals(entry.InputName, typeName, StringComparison.OrdinalIgnoreCase) && - entry.CustomType is not null && - !string.Equals(entry.CustomType.Name, typeName, StringComparison.OrdinalIgnoreCase)); + entry.CustomTypeName is not null && + !string.Equals(entry.CustomTypeName, typeName, StringComparison.OrdinalIgnoreCase)); } private protected override TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) @@ -1966,7 +1966,8 @@ private static string BuildAdditionalTypePropertiesFieldName(CSharpType addition /// /// Indexes input and customized type names once per generator for collision lookups. - /// The input namespace and customization compilations must be finalized before the first lookup. + /// The input namespace and customization compilations must be finalized before the first lookup; + /// the frozen maps are not refreshed during the generator lifetime. /// internal sealed class NameCache { @@ -2005,17 +2006,17 @@ private IReadOnlyDictionary> BuildTypeMap( var inputName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); var typeNamespace = GetNamespace(getNamespace(inputType)); var customType = FindCustomizationType(typeNamespace, GetCustomizationLookupNames(inputType, inputName)); - var customTypeProvider = customType?.Type; + var customTypeName = customType?.Type.Name; var entry = new TypeEntry( inputType, inputName, - customTypeProvider, + customTypeName, customType?.IsResultAlias == true && HasLastContractName(typeNamespace, inputName)); Add(cache, inputName, entry); - if (customTypeProvider is not null && - !string.Equals(customTypeProvider.Name, inputName, StringComparison.OrdinalIgnoreCase)) + if (customTypeName is not null && + !string.Equals(customTypeName, inputName, StringComparison.OrdinalIgnoreCase)) { - Add(cache, customTypeProvider.Name, entry); + Add(cache, customTypeName, entry); } } @@ -2115,7 +2116,7 @@ private static IReadOnlyDictionary> Freeze(Dictionar internal sealed class TypeEntry( InputType inputType, string inputName, - TypeProvider? customType, + string? customTypeName, bool hasLastContractAliasName) { /// @@ -2129,9 +2130,9 @@ internal sealed class TypeEntry( internal string InputName { get; } = inputName; /// - /// Gets the customization resolved from input, acronym, or result-alias lookup names. + /// Gets the customization type name resolved from input, acronym, or result-alias lookup names. /// - internal TypeProvider? CustomType { get; } = customType; + internal string? CustomTypeName { get; } = customTypeName; /// /// Gets whether a result-alias customization maps to an input name in the last contract. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index 45f62951ee4..17dcbd3808f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -30,9 +30,6 @@ private ChangeTrackingDictionaryDefinition ChangeTrackingDictionaryProvider private Dictionary InputTypeToModelProvider { get; } = []; - private readonly Lazy _modelProviderNameCache = new(() => new(CodeModelGenerator.Instance)); - internal ModelProvider.NameCache ModelProviderNameCache => _modelProviderNameCache.Value; - public IDictionary CSharpTypeMap { get; } = new Dictionary(CSharpType.IgnoreNullableComparer); // Maps C# type names to TypeProviders for efficient lookup when resolving types by name From f7dc006a0de5dd4ca1290654c8fb19068b3bed58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:10:46 +0000 Subject: [PATCH 17/68] refactor(csharp): avoid copying name cache entries Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../Microsoft.TypeSpec.Generator/src/CodeModelGenerator.cs | 4 ++++ .../src/Providers/ModelProvider.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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 057e7e5d7ae..000d66327a0 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 @@ -83,6 +83,10 @@ protected CodeModelGenerator() // Extensibility points to be implemented by a generator public virtual TypeFactory TypeFactory { get; } + + /// + /// Gets the name cache after the input namespace and customization compilations are finalized. + /// internal ModelProvider.NameCache ModelProviderNameCache => _modelProviderNameCache.Value; private SourceInputModel? _sourceInputModel; 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 cf44b862463..00467339c0b 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 @@ -2110,7 +2110,7 @@ private static void Add(Dictionary> cache, string name, T ent private static IReadOnlyDictionary> Freeze(Dictionary> cache) => cache.ToDictionary( pair => pair.Key, - pair => (IReadOnlyList)pair.Value.ToArray(), + pair => (IReadOnlyList)pair.Value.AsReadOnly(), StringComparer.OrdinalIgnoreCase); internal sealed class TypeEntry( From 8df38f897bafa1604e75285c431b50d0fe48949f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:22:15 +0000 Subject: [PATCH 18/68] fix(csharp): simplify deterministic result collisions Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/CodeModelGenerator.cs | 8 - .../src/Providers/ModelProvider.cs | 353 +++++++----------- .../ModelProviders/ModelProviderTests.cs | 18 +- 3 files changed, 148 insertions(+), 231 deletions(-) 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 000d66327a0..5f668649e81 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 @@ -27,7 +27,6 @@ public abstract class CodeModelGenerator private List _visitors = []; private List _additionalMetadataReferences = []; private readonly Dictionary> _customCodeMethodDependencies = new(StringComparer.Ordinal); - private readonly Lazy _modelProviderNameCache; private static CodeModelGenerator? _instance; private List _sharedSourceDirectories = []; public const string GeneratorMetadataName = "GeneratorName"; @@ -64,7 +63,6 @@ public CodeModelGenerator(GeneratorContext context) _inputLibrary = new InputLibrary(Configuration.OutputDirectory); TypeFactory = new TypeFactory(); Emitter = new Emitter(Console.OpenStandardOutput()); - _modelProviderNameCache = new(() => new(this)); } // for mocking @@ -72,7 +70,6 @@ public CodeModelGenerator(GeneratorContext context) protected CodeModelGenerator() #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { - _modelProviderNameCache = new(() => new(this)); } internal bool IsNewProject { get; set; } @@ -84,11 +81,6 @@ protected CodeModelGenerator() // Extensibility points to be implemented by a generator public virtual TypeFactory TypeFactory { get; } - /// - /// Gets the name cache after the input namespace and customization compilations are finalized. - /// - internal ModelProvider.NameCache ModelProviderNameCache => _modelProviderNameCache.Value; - private SourceInputModel? _sourceInputModel; private List _rewriters = []; 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 00467339c0b..5cb37140f02 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 @@ -335,40 +335,129 @@ private protected override string NormalizeTypeName(string name) } var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; - return HasConflictingResultName(typeNamespace, resultName) + var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; + return HasConflictingResultName(inputNamespace, typeNamespace, resultName) ? normalizedName : resultName; } - private bool HasConflictingResultName(string typeNamespace, string resultName) - { - var nameCache = CodeModelGenerator.Instance.ModelProviderNameCache; + private bool HasConflictingResultName(InputNamespace inputNamespace, string typeNamespace, string resultName) // Model and enum files share a flat output directory, even across namespaces. - return nameCache.FindModels(resultName).Any(entry => HasConflictingName(entry, resultName)) || - nameCache.FindEnums(resultName).Any(entry => HasConflictingName(entry, resultName)) || - nameCache.FindClients(resultName).Any(entry => HasConflictingName(entry, typeNamespace, resultName)); - } + => inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || + inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || + inputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)); - private bool HasConflictingName(NameCache.TypeEntry entry, string resultName) + private bool HasConflictingName(InputType inputType, string inputTypeNamespace, string resultName) { - if (entry.InputType == _inputModel) + if (inputType == _inputModel) + { + return false; + } + + var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); + var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + // Acronym normalization only changes casing, so this also covers the normalized filename. + var hasMatchingInputName = string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase); + var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); + var customType = sourceInputModel.Customization is null + ? null + : FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); + if (customType is not null) + { + return string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); + } + + if (hasMatchingInputName) + { + return true; + } + + if (inputType is not InputModelType otherModel || inputType.IsExactName) { return false; } - if (entry.CustomTypeName is null) + var normalizedName = otherName.NormalizeCSharpAcronyms(); + if (!normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || + !string.Equals($"{normalizedName[..^ResponseSuffix.Length]}Result", resultName, StringComparison.OrdinalIgnoreCase) || + HasLastContractName(otherNamespace, otherName)) + { + return false; + } + + return CompareModelIdentity(otherModel, _inputModel) < 0; + } + + private static int CompareModelIdentity(InputModelType left, InputModelType right) + { + var namespaceComparison = string.Compare(left.Namespace, right.Namespace, StringComparison.Ordinal); + return namespaceComparison != 0 + ? namespaceComparison + : string.Compare(left.CrossLanguageDefinitionId, right.CrossLanguageDefinitionId, StringComparison.Ordinal); + } + + private bool HasConflictingName(InputClient client, string typeNamespace, string resultName) + { + var clientNamespace = string.IsNullOrEmpty(client.Namespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(client.Namespace); + var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); + var customType = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); + return (customType?.Type.Namespace ?? clientNamespace) == typeNamespace && + (customType?.Name ?? clientName) == resultName; + } + + private TypeProvider? FindCustomizationType( + string typeNamespace, + IEnumerable lookupNames) + { + var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + foreach (var lookupName in lookupNames) + { + var customType = sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, lookupName); + if (customType is not null) + { + return customType; + } + } + + return null; + } + + private static IEnumerable GetCustomizationLookupNames(InputType inputType, string name) + { + yield return name; + if (inputType.IsExactName) + { + yield break; + } + + var normalizedName = name.NormalizeCSharpAcronyms(); + if (normalizedName != name) { - // Acronym normalization only changes casing, so this also covers the normalized filename. - return string.Equals(entry.InputName, resultName, StringComparison.OrdinalIgnoreCase); + yield return normalizedName; } - return string.Equals(entry.CustomTypeName, resultName, StringComparison.OrdinalIgnoreCase) && - !entry.HasLastContractAliasName; + if (inputType is InputModelType && normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) + { + yield return $"{normalizedName[..^ResponseSuffix.Length]}Result"; + } } - private static bool HasConflictingName(NameCache.ClientEntry entry, string typeNamespace, string resultName) - => (entry.CustomType?.Type.Namespace ?? entry.Namespace) == typeNamespace && - (entry.CustomType?.Name ?? entry.InputName) == resultName; + private bool HasLastContractName(string typeNamespace, string name) + { + var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + if (sourceInputModel.FindForTypeInLastContract(typeNamespace, name) is not null) + { + return true; + } + + var normalizedName = name.NormalizeCSharpAcronyms(); + return normalizedName != name && + sourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName) is not null; + } private protected override TypeProvider? BuildCustomCodeView(string? generatedTypeName = null, string? generatedTypeNamespace = null) { @@ -390,12 +479,30 @@ private bool HasCustomizedSiblingInputName(string typeName) return false; } - var nameCache = CodeModelGenerator.Instance.ModelProviderNameCache; - return nameCache.FindModels(typeName).Concat(nameCache.FindEnums(typeName)).Any(entry => - entry.InputType != _inputModel && - string.Equals(entry.InputName, typeName, StringComparison.OrdinalIgnoreCase) && - entry.CustomTypeName is not null && - !string.Equals(entry.CustomTypeName, typeName, StringComparison.OrdinalIgnoreCase)); + var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; + return inputNamespace.Models.Any(model => HasCustomizedSiblingInputName(model, model.Namespace, typeName)) || + inputNamespace.Enums.Any(@enum => HasCustomizedSiblingInputName(@enum, @enum.Namespace, typeName)); + } + + private bool HasCustomizedSiblingInputName(InputType inputType, string inputTypeNamespace, string typeName) + { + if (inputType == _inputModel) + { + return false; + } + + var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); + if (!string.Equals(otherName, typeName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); + var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); + return customType is not null && + !string.Equals(customType.Name, typeName, StringComparison.OrdinalIgnoreCase); } private protected override TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) @@ -1963,203 +2070,5 @@ private static string BuildAdditionalTypePropertiesFieldName(CSharpType addition return $"_additional{name.ToIdentifierName()}Properties"; } - - /// - /// Indexes input and customized type names once per generator for collision lookups. - /// The input namespace and customization compilations must be finalized before the first lookup; - /// the frozen maps are not refreshed during the generator lifetime. - /// - internal sealed class NameCache - { - private readonly CodeModelGenerator _generator; - private readonly Lazy>> _models; - private readonly Lazy>> _enums; - private readonly Lazy>> _clients; - - internal NameCache(CodeModelGenerator generator) - { - _generator = generator; - _models = new(() => BuildTypeMap( - generator.InputLibrary.InputNamespace.Models, - model => model.Namespace)); - _enums = new(() => BuildTypeMap( - generator.InputLibrary.InputNamespace.Enums, - @enum => @enum.Namespace)); - _clients = new(BuildClientMap); - } - - internal IReadOnlyList FindModels(string name) => Find(_models.Value, name); - internal IReadOnlyList FindEnums(string name) => Find(_enums.Value, name); - internal IReadOnlyList FindClients(string name) => Find(_clients.Value, name); - - private static IReadOnlyList Find(IReadOnlyDictionary> cache, string name) - => cache.TryGetValue(name, out var entries) ? entries : []; - - private IReadOnlyDictionary> BuildTypeMap( - IEnumerable inputTypes, - Func getNamespace) - where T : InputType - { - var cache = new Dictionary>(StringComparer.OrdinalIgnoreCase); - foreach (var inputType in inputTypes) - { - var inputName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); - var typeNamespace = GetNamespace(getNamespace(inputType)); - var customType = FindCustomizationType(typeNamespace, GetCustomizationLookupNames(inputType, inputName)); - var customTypeName = customType?.Type.Name; - var entry = new TypeEntry( - inputType, - inputName, - customTypeName, - customType?.IsResultAlias == true && HasLastContractName(typeNamespace, inputName)); - Add(cache, inputName, entry); - if (customTypeName is not null && - !string.Equals(customTypeName, inputName, StringComparison.OrdinalIgnoreCase)) - { - Add(cache, customTypeName, entry); - } - } - - return Freeze(cache); - } - - private IReadOnlyDictionary> BuildClientMap() - { - var cache = new Dictionary>(StringComparer.OrdinalIgnoreCase); - foreach (var client in _generator.InputLibrary.InputNamespace.Clients) - { - var inputName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - var typeNamespace = GetNamespace(client.Namespace); - var customType = _generator.SourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, inputName); - var entry = new ClientEntry(typeNamespace, inputName, customType); - Add(cache, inputName, entry); - if (customType is not null && - !string.Equals(customType.Name, inputName, StringComparison.OrdinalIgnoreCase)) - { - Add(cache, customType.Name, entry); - } - } - - return Freeze(cache); - } - - private string GetNamespace(string inputNamespace) - => string.IsNullOrEmpty(inputNamespace) - ? _generator.TypeFactory.PrimaryNamespace - : _generator.TypeFactory.GetCleanNameSpace(inputNamespace); - - private (TypeProvider Type, bool IsResultAlias)? FindCustomizationType( - string typeNamespace, - IEnumerable<(string Name, bool IsResultAlias)> lookupNames) - { - foreach (var lookupName in lookupNames) - { - var customType = _generator.SourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, lookupName.Name); - if (customType is not null) - { - return (customType, lookupName.IsResultAlias); - } - } - - return null; - } - - private static IEnumerable<(string Name, bool IsResultAlias)> GetCustomizationLookupNames(InputType inputType, string name) - { - yield return (name, false); - if (inputType.IsExactName) - { - yield break; - } - - var normalizedName = name.NormalizeCSharpAcronyms(); - if (normalizedName != name) - { - yield return (normalizedName, false); - } - - if (inputType is InputModelType && normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) - { - yield return ($"{normalizedName[..^ResponseSuffix.Length]}Result", true); - } - } - - private bool HasLastContractName(string typeNamespace, string name) - { - if (_generator.SourceInputModel.FindForTypeInLastContract(typeNamespace, name) is not null) - { - return true; - } - - var normalizedName = name.NormalizeCSharpAcronyms(); - return normalizedName != name && - _generator.SourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName) is not null; - } - - private static void Add(Dictionary> cache, string name, T entry) - { - if (!cache.TryGetValue(name, out var entries)) - { - entries = []; - cache.Add(name, entries); - } - - entries.Add(entry); - } - - private static IReadOnlyDictionary> Freeze(Dictionary> cache) - => cache.ToDictionary( - pair => pair.Key, - pair => (IReadOnlyList)pair.Value.AsReadOnly(), - StringComparer.OrdinalIgnoreCase); - - internal sealed class TypeEntry( - InputType inputType, - string inputName, - string? customTypeName, - bool hasLastContractAliasName) - { - /// - /// Gets the input type represented by this entry. - /// - internal InputType InputType { get; } = inputType; - - /// - /// Gets the input name after identifier normalization, unless the name is exact. - /// - internal string InputName { get; } = inputName; - - /// - /// Gets the customization type name resolved from input, acronym, or result-alias lookup names. - /// - internal string? CustomTypeName { get; } = customTypeName; - - /// - /// Gets whether a result-alias customization maps to an input name in the last contract. - /// - internal bool HasLastContractAliasName { get; } = hasLastContractAliasName; - } - - internal sealed class ClientEntry( - string @namespace, - string inputName, - TypeProvider? customType) - { - /// - /// Gets the cleaned client namespace. - /// - internal string Namespace { get; } = @namespace; - - /// - /// Gets the client name after identifier normalization, unless the name is exact. - /// - internal string InputName { get; } = inputName; - - /// - /// Gets the customization resolved from the client input name. - /// - internal TypeProvider? CustomType { get; } = customType; - } - } } } 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 98490dc7204..a0eb536cbe8 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 @@ -283,6 +283,22 @@ public void TestBuildName_ResponseSuffixAvoidsModelCollision( CollectionAssert.AreEquivalent(new[] { "WidgetResponse", resultName }, providers.Select(p => p.Name)); } + [TestCase(false)] + [TestCase(true)] + public void TestBuildName_ResponseSuffixSelectsSingleResultForDuplicateEffectiveNames(bool reverseOrder) + { + var first = InputFactory.Model("FooResponse", @namespace: "Alpha"); + var second = InputFactory.Model("FooResponse", @namespace: "Beta"); + InputModelType[] models = reverseOrder ? [second, first] : [first, second]; + MockHelpers.LoadMockGenerator(inputModelTypes: models); + + var firstProvider = CodeModelGenerator.Instance.TypeFactory.CreateModel(first)!; + var secondProvider = CodeModelGenerator.Instance.TypeFactory.CreateModel(second)!; + + Assert.AreEqual("FooResult", firstProvider.Name); + Assert.AreEqual("FooResponse", secondProvider.Name); + } + [TestCase("Sample.Models", "WidgetResponse")] [TestCase("Other", "WidgetResult")] public void TestBuildName_ResponseSuffixAvoidsClientCollision(string clientNamespace, string expectedName) @@ -368,7 +384,7 @@ await MockHelpers.LoadMockGeneratorAsync( var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); - CollectionAssert.AreEquivalent(new[] { "WidgetResult", "IPResponse" }, providers.Select(p => p.Name)); + CollectionAssert.AreEquivalent(new[] { "WidgetResponse", "IPResponse" }, providers.Select(p => p.Name)); } [Test] From 0c7dd43fe125339557eeaf4ddffb008032a97dfd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:28:19 +0000 Subject: [PATCH 19/68] fix(csharp): preserve shipped result name ownership Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 10 ++++++++++ .../ModelProviders/ModelProviderTests.cs | 18 ++++++++++++++++++ .../FooResult.cs | 6 ++++++ 3 files changed, 34 insertions(+) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesResultOwnerFromLastContract/FooResult.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 5cb37140f02..9f76cda4b54 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 @@ -387,6 +387,16 @@ private bool HasConflictingName(InputType inputType, string inputTypeNamespace, return false; } + if (HasLastContractName(otherNamespace, resultName)) + { + return true; + } + + if (HasLastContractName(BuildNamespace(), resultName)) + { + return false; + } + return CompareModelIdentity(otherModel, _inputModel) < 0; } 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 a0eb536cbe8..d36147c447c 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 @@ -299,6 +299,24 @@ public void TestBuildName_ResponseSuffixSelectsSingleResultForDuplicateEffective Assert.AreEqual("FooResponse", secondProvider.Name); } + [TestCase(false)] + [TestCase(true)] + public async Task TestBuildName_ResponseSuffixPreservesResultOwnerFromLastContract(bool reverseOrder) + { + var first = InputFactory.Model("FooResponse", @namespace: "Alpha"); + var shipped = InputFactory.Model("FooResponse", @namespace: "Beta"); + InputModelType[] models = reverseOrder ? [shipped, first] : [first, shipped]; + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: models, + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var firstProvider = CodeModelGenerator.Instance.TypeFactory.CreateModel(first)!; + var shippedProvider = CodeModelGenerator.Instance.TypeFactory.CreateModel(shipped)!; + + Assert.AreEqual("FooResponse", firstProvider.Name); + Assert.AreEqual("FooResult", shippedProvider.Name); + } + [TestCase("Sample.Models", "WidgetResponse")] [TestCase("Other", "WidgetResult")] public void TestBuildName_ResponseSuffixAvoidsClientCollision(string clientNamespace, string expectedName) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesResultOwnerFromLastContract/FooResult.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesResultOwnerFromLastContract/FooResult.cs new file mode 100644 index 00000000000..d9a0b576259 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesResultOwnerFromLastContract/FooResult.cs @@ -0,0 +1,6 @@ +namespace Beta +{ + public partial class FooResult + { + } +} From 052c3f786b9dca392f4e92d84f25b335441cbffe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:46:30 +0000 Subject: [PATCH 20/68] refactor(csharp): clarify result ownership rules Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) 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 9f76cda4b54..124292c6d26 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 @@ -341,8 +341,8 @@ private protected override string NormalizeTypeName(string name) : resultName; } + // Model and enum files share a flat output directory, even across namespaces. private bool HasConflictingResultName(InputNamespace inputNamespace, string typeNamespace, string resultName) - // Model and enum files share a flat output directory, even across namespaces. => inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || inputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)); @@ -356,11 +356,7 @@ private bool HasConflictingName(InputType inputType, string inputTypeNamespace, var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; - // Acronym normalization only changes casing, so this also covers the normalized filename. - var hasMatchingInputName = string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase); - var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); + var otherNamespace = GetTypeNamespace(inputTypeNamespace); var customType = sourceInputModel.Customization is null ? null : FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); @@ -369,7 +365,8 @@ private bool HasConflictingName(InputType inputType, string inputTypeNamespace, return string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); } - if (hasMatchingInputName) + // Acronym normalization only changes casing, so this also covers the normalized filename. + if (string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase)) { return true; } @@ -387,6 +384,8 @@ private bool HasConflictingName(InputType inputType, string inputTypeNamespace, return false; } + // Preserve existing names first, then give historical Result ownership precedence, + // and finally choose a stable owner for new competing Response models. if (HasLastContractName(otherNamespace, resultName)) { return true; @@ -410,9 +409,7 @@ private static int CompareModelIdentity(InputModelType left, InputModelType righ private bool HasConflictingName(InputClient client, string typeNamespace, string resultName) { - var clientNamespace = string.IsNullOrEmpty(client.Namespace) - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(client.Namespace); + var clientNamespace = GetTypeNamespace(client.Namespace); var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); var customType = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); return (customType?.Type.Namespace ?? clientNamespace) == typeNamespace && @@ -507,14 +504,17 @@ private bool HasCustomizedSiblingInputName(InputType inputType, string inputType return false; } - var otherNamespace = string.IsNullOrEmpty(inputTypeNamespace) - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputTypeNamespace); + var otherNamespace = GetTypeNamespace(inputTypeNamespace); var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); return customType is not null && !string.Equals(customType.Name, typeName, StringComparison.OrdinalIgnoreCase); } + private static string GetTypeNamespace(string inputNamespace) + => string.IsNullOrEmpty(inputNamespace) + ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace + : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputNamespace); + private protected override TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) { var typeNamespace = generatedTypeNamespace ?? CustomCodeView?.Type.Namespace ?? BuildNamespace(); From 75d51a028104dc97241965a371bd1e1417e6d063 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:48:13 +0000 Subject: [PATCH 21/68] refactor(csharp): centralize customization lookup guard Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 124292c6d26..e3fbcc992bd 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 @@ -355,11 +355,8 @@ private bool HasConflictingName(InputType inputType, string inputTypeNamespace, } var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); - var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; var otherNamespace = GetTypeNamespace(inputTypeNamespace); - var customType = sourceInputModel.Customization is null - ? null - : FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); + var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); if (customType is not null) { return string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); @@ -401,6 +398,7 @@ private bool HasConflictingName(InputType inputType, string inputTypeNamespace, private static int CompareModelIdentity(InputModelType left, InputModelType right) { + // The lower stable identity claims the Result name. var namespaceComparison = string.Compare(left.Namespace, right.Namespace, StringComparison.Ordinal); return namespaceComparison != 0 ? namespaceComparison @@ -421,6 +419,11 @@ private bool HasConflictingName(InputClient client, string typeNamespace, string IEnumerable lookupNames) { var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; + if (sourceInputModel.Customization is null) + { + return null; + } + foreach (var lookupName in lookupNames) { var customType = sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, lookupName); From 72192c8960f8726553c7dabe04f4a057c1fd7350 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:04:46 +0000 Subject: [PATCH 22/68] refactor(csharp): unify model naming helpers Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 e3fbcc992bd..9a6077e42c7 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 @@ -245,10 +245,7 @@ protected FieldProvider? RawDataField /// public ConstructorProvider FullConstructor => _fullConstructor ??= BuildFullConstructor(); - protected override string BuildNamespace() => string.IsNullOrEmpty(_inputModel.Namespace) ? - // TODO remove null check once https://github.com/Azure/typespec-azure/issues/2209 is fixed. - CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace : - CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(_inputModel.Namespace); + protected override string BuildNamespace() => GetTypeNamespace(_inputModel.Namespace); protected override CSharpType? BuildBaseType() { @@ -336,13 +333,13 @@ private protected override string NormalizeTypeName(string name) var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - return HasConflictingResultName(inputNamespace, typeNamespace, resultName) + return HasConflictingName(inputNamespace, typeNamespace, resultName) ? normalizedName : resultName; } // Model and enum files share a flat output directory, even across namespaces. - private bool HasConflictingResultName(InputNamespace inputNamespace, string typeNamespace, string resultName) + private bool HasConflictingName(InputNamespace inputNamespace, string typeNamespace, string resultName) => inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || inputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)); @@ -515,6 +512,7 @@ private bool HasCustomizedSiblingInputName(InputType inputType, string inputType private static string GetTypeNamespace(string inputNamespace) => string.IsNullOrEmpty(inputNamespace) + // TODO remove null check once https://github.com/Azure/typespec-azure/issues/2209 is fixed. ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputNamespace); From c1097402c92c52c78462ef8b2719c2baaecb1685 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 18:05:33 +0000 Subject: [PATCH 23/68] refactor(csharp): clarify model name conflicts Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 9a6077e42c7..d8d6c6a2467 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 @@ -333,13 +333,13 @@ private protected override string NormalizeTypeName(string name) var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - return HasConflictingName(inputNamespace, typeNamespace, resultName) + return HasConflictingNameInLibrary(inputNamespace, typeNamespace, resultName) ? normalizedName : resultName; } // Model and enum files share a flat output directory, even across namespaces. - private bool HasConflictingName(InputNamespace inputNamespace, string typeNamespace, string resultName) + private bool HasConflictingNameInLibrary(InputNamespace inputNamespace, string typeNamespace, string resultName) => inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || inputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)); From 7a1ef8471f6d257772a781f3cc59988366fba210 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:26:53 +0000 Subject: [PATCH 24/68] fix(csharp): reserve standalone result customizations Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 11 ++++++++++- .../Providers/ModelProviders/ModelProviderTests.cs | 14 ++++++++++++++ .../WidgetResult.cs | 6 ++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsStandaloneCustomizationCollision/WidgetResult.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 d8d6c6a2467..8a957c690d8 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 @@ -333,11 +333,20 @@ private protected override string NormalizeTypeName(string name) var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - return HasConflictingNameInLibrary(inputNamespace, typeNamespace, resultName) + return HasPhysicalCustomizationName(typeNamespace, resultName) || + HasConflictingNameInLibrary(inputNamespace, typeNamespace, resultName) ? normalizedName : resultName; } + private bool HasPhysicalCustomizationName(string typeNamespace, string name) + { + var customType = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( + typeNamespace, name, DeclaringTypeName); + return customType is not null && + string.Equals(customType.Name, name, StringComparison.OrdinalIgnoreCase); + } + // Model and enum files share a flat output directory, even across namespaces. private bool HasConflictingNameInLibrary(InputNamespace inputNamespace, string typeNamespace, string resultName) => inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || 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 d36147c447c..c31b249cfa4 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 @@ -355,6 +355,20 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(other)!.Name); } + [Test] + public async Task TestBuildName_ResponseSuffixAvoidsStandaloneCustomizationCollision() + { + var response = InputFactory.Model("WidgetResponse"); + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [response], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var provider = CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!; + + Assert.AreEqual("WidgetResponse", provider.Name); + Assert.IsNull(provider.CustomCodeView); + } + [Test] public async Task TestBuildName_ResponseSuffixAllowsCustomizedModelResultName() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsStandaloneCustomizationCollision/WidgetResult.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsStandaloneCustomizationCollision/WidgetResult.cs new file mode 100644 index 00000000000..33ed5f9f2b8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsStandaloneCustomizationCollision/WidgetResult.cs @@ -0,0 +1,6 @@ +namespace Sample.Models +{ + public partial class WidgetResult + { + } +} From adaf04887f252f7aee39c95f88c554d3c95e00b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:38:02 +0000 Subject: [PATCH 25/68] refactor(csharp): inline customization reservation Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) 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 8a957c690d8..7ecde5e0a2f 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 @@ -333,20 +333,13 @@ private protected override string NormalizeTypeName(string name) var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - return HasPhysicalCustomizationName(typeNamespace, resultName) || + var customType = sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, resultName, DeclaringTypeName); + return (customType is not null && string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase)) || HasConflictingNameInLibrary(inputNamespace, typeNamespace, resultName) ? normalizedName : resultName; } - private bool HasPhysicalCustomizationName(string typeNamespace, string name) - { - var customType = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( - typeNamespace, name, DeclaringTypeName); - return customType is not null && - string.Equals(customType.Name, name, StringComparison.OrdinalIgnoreCase); - } - // Model and enum files share a flat output directory, even across namespaces. private bool HasConflictingNameInLibrary(InputNamespace inputNamespace, string typeNamespace, string resultName) => inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || From 152c949685b79d383b8b586f802c830db319eb7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:50:34 +0000 Subject: [PATCH 26/68] fix(csharp): isolate customized client model names Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ClientProviderCustomizationTests.cs | 19 +++++++++++++++++++ .../CustomWidgetClient.cs | 9 +++++++++ .../src/Providers/ModelProvider.cs | 17 ++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs index bdf17516e28..f54983213a9 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs @@ -17,6 +17,25 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ClientProvide { public class ClientProviderCustomizationTests { + [Test] + public async Task ModelDoesNotUseClientCustomization() + { + var inputModel = InputFactory.Model("WidgetResponse"); + var inputClient = InputFactory.Client("WidgetResult"); + inputClient.Update(@namespace: "Sample.Models"); + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [inputModel], + clients: () => [inputClient], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; + var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + + Assert.AreEqual("WidgetResult", modelProvider.Name); + Assert.IsNull(modelProvider.CustomCodeView); + Assert.AreEqual("CustomWidgetClient", clientProvider.CustomCodeView?.Name); + } + [Test] public async Task CanAddMethod() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs new file mode 100644 index 00000000000..e9e6c61e62b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs @@ -0,0 +1,9 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("WidgetResult")] + public partial class CustomWidgetClient + { + } +} 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 7ecde5e0a2f..9baa3ffba21 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 @@ -490,7 +490,8 @@ private bool HasCustomizedSiblingInputName(string typeName) var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; return inputNamespace.Models.Any(model => HasCustomizedSiblingInputName(model, model.Namespace, typeName)) || - inputNamespace.Enums.Any(@enum => HasCustomizedSiblingInputName(@enum, @enum.Namespace, typeName)); + inputNamespace.Enums.Any(@enum => HasCustomizedSiblingInputName(@enum, @enum.Namespace, typeName)) || + inputNamespace.Clients.Any(client => HasCustomizedSiblingInputName(client, typeName)); } private bool HasCustomizedSiblingInputName(InputType inputType, string inputTypeNamespace, string typeName) @@ -512,6 +513,20 @@ private bool HasCustomizedSiblingInputName(InputType inputType, string inputType !string.Equals(customType.Name, typeName, StringComparison.OrdinalIgnoreCase); } + private bool HasCustomizedSiblingInputName(InputClient client, string typeName) + { + var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); + if (!string.Equals(clientName, typeName, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var clientNamespace = GetTypeNamespace(client.Namespace); + var customType = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); + return customType is not null && + !string.Equals(customType.Name, typeName, StringComparison.OrdinalIgnoreCase); + } + private static string GetTypeNamespace(string inputNamespace) => string.IsNullOrEmpty(inputNamespace) // TODO remove null check once https://github.com/Azure/typespec-azure/issues/2209 is fixed. From 9cd342a1e4c2c297152676d734b76cd16ecb70c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:58:30 +0000 Subject: [PATCH 27/68] refactor(csharp): unify customization collision checks Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) 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 9baa3ffba21..5f1d7859635 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 @@ -502,27 +502,36 @@ private bool HasCustomizedSiblingInputName(InputType inputType, string inputType } var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); - if (!string.Equals(otherName, typeName, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - var otherNamespace = GetTypeNamespace(inputTypeNamespace); - var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); - return customType is not null && - !string.Equals(customType.Name, typeName, StringComparison.OrdinalIgnoreCase); + return HasCustomizedSiblingInputName( + otherName, + otherNamespace, + typeName, + GetCustomizationLookupNames(inputType, otherName)); } private bool HasCustomizedSiblingInputName(InputClient client, string typeName) { var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - if (!string.Equals(clientName, typeName, StringComparison.OrdinalIgnoreCase)) + return HasCustomizedSiblingInputName( + clientName, + GetTypeNamespace(client.Namespace), + typeName, + [clientName]); + } + + private bool HasCustomizedSiblingInputName( + string siblingName, + string siblingNamespace, + string typeName, + IEnumerable customizationLookupNames) + { + if (!string.Equals(siblingName, typeName, StringComparison.OrdinalIgnoreCase)) { return false; } - var clientNamespace = GetTypeNamespace(client.Namespace); - var customType = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); + var customType = FindCustomizationType(siblingNamespace, customizationLookupNames); return customType is not null && !string.Equals(customType.Name, typeName, StringComparison.OrdinalIgnoreCase); } From da5c60c5472ab06bb29c536887445427cb109bd6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 20:21:10 +0000 Subject: [PATCH 28/68] refactor(csharp): simplify model collision checks Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) 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 5f1d7859635..cec1c023a36 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 @@ -332,19 +332,18 @@ private protected override string NormalizeTypeName(string name) } var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; - var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; var customType = sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, resultName, DeclaringTypeName); return (customType is not null && string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase)) || - HasConflictingNameInLibrary(inputNamespace, typeNamespace, resultName) + HasConflictingName(CodeModelGenerator.Instance.InputLibrary, typeNamespace, resultName) ? normalizedName : resultName; } // Model and enum files share a flat output directory, even across namespaces. - private bool HasConflictingNameInLibrary(InputNamespace inputNamespace, string typeNamespace, string resultName) - => inputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || - inputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || - inputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)); + private bool HasConflictingName(InputLibrary inputLibrary, string typeNamespace, string resultName) + => inputLibrary.InputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || + inputLibrary.InputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || + inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)); private bool HasConflictingName(InputType inputType, string inputTypeNamespace, string resultName) { @@ -489,38 +488,38 @@ private bool HasCustomizedSiblingInputName(string typeName) } var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - return inputNamespace.Models.Any(model => HasCustomizedSiblingInputName(model, model.Namespace, typeName)) || - inputNamespace.Enums.Any(@enum => HasCustomizedSiblingInputName(@enum, @enum.Namespace, typeName)) || - inputNamespace.Clients.Any(client => HasCustomizedSiblingInputName(client, typeName)); - } - - private bool HasCustomizedSiblingInputName(InputType inputType, string inputTypeNamespace, string typeName) - { - if (inputType == _inputModel) + foreach (var (inputType, inputTypeNamespace) in inputNamespace.Models + .Select(model => ((InputType)model, model.Namespace)) + .Concat(inputNamespace.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) { - return false; - } + if (inputType == _inputModel) + { + continue; + } - var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); - var otherNamespace = GetTypeNamespace(inputTypeNamespace); - return HasCustomizedSiblingInputName( - otherName, - otherNamespace, - typeName, - GetCustomizationLookupNames(inputType, otherName)); - } + var inputTypeName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); + if (HasMismatchedCustomization( + inputTypeName, + GetTypeNamespace(inputTypeNamespace), + typeName, + GetCustomizationLookupNames(inputType, inputTypeName))) + { + return true; + } + } - private bool HasCustomizedSiblingInputName(InputClient client, string typeName) - { - var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - return HasCustomizedSiblingInputName( - clientName, - GetTypeNamespace(client.Namespace), - typeName, - [clientName]); + return inputNamespace.Clients.Any(client => + { + var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); + return HasMismatchedCustomization( + clientName, + GetTypeNamespace(client.Namespace), + typeName, + [clientName]); + }); } - private bool HasCustomizedSiblingInputName( + private bool HasMismatchedCustomization( string siblingName, string siblingNamespace, string typeName, From f4511d039eff9e07347351e7dd4f39c31522f2ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 20:42:24 +0000 Subject: [PATCH 29/68] fix(csharp): cover hidden model name collisions Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 21 ++++++++++-- .../ModelProviders/ModelProviderTests.cs | 32 +++++++++++++++++++ .../FooResult.cs | 6 ++++ .../CustomFooResult.cs | 9 ++++++ 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName(LastContract)/FooResult.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName/CustomFooResult.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 cec1c023a36..6feffc50410 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 @@ -341,10 +341,24 @@ private protected override string NormalizeTypeName(string name) // Model and enum files share a flat output directory, even across namespaces. private bool HasConflictingName(InputLibrary inputLibrary, string typeNamespace, string resultName) - => inputLibrary.InputNamespace.Models.Any(model => HasConflictingName(model, model.Namespace, resultName)) || + => GetEmittedModels(inputLibrary).Any(model => HasConflictingName(model, model.Namespace, resultName)) || inputLibrary.InputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)); + private static IEnumerable GetEmittedModels(InputLibrary inputLibrary) + { + foreach (var model in inputLibrary.InputNamespace.Models) + { + yield return model; + + var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(model => model.IsUnknownDiscriminatorModel); + if (unknownVariant is not null) + { + yield return unknownVariant; + } + } + } + private bool HasConflictingName(InputType inputType, string inputTypeNamespace, string resultName) { if (inputType == _inputModel) @@ -357,7 +371,8 @@ private bool HasConflictingName(InputType inputType, string inputTypeNamespace, var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); if (customType is not null) { - return string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); + return HasLastContractName(otherNamespace, resultName) || + string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); } // Acronym normalization only changes casing, so this also covers the normalized filename. @@ -488,7 +503,7 @@ private bool HasCustomizedSiblingInputName(string typeName) } var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - foreach (var (inputType, inputTypeNamespace) in inputNamespace.Models + foreach (var (inputType, inputTypeNamespace) in GetEmittedModels(CodeModelGenerator.Instance.InputLibrary) .Select(model => ((InputType)model, model.Namespace)) .Concat(inputNamespace.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) { 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 c31b249cfa4..1354558786c 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 @@ -382,6 +382,38 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.AreEqual("CustomWidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(result)!.Name); } + [Test] + public void TestBuildName_ResponseSuffixAvoidsUnknownDiscriminatorCollision() + { + var discriminator = InputFactory.Property("kind", InputPrimitiveType.String, isDiscriminator: true); + var result = InputFactory.Model( + "FooResult", + properties: [discriminator], + discriminatedModels: new Dictionary()); + var response = InputFactory.Model("UnknownFooResponse"); + MockHelpers.LoadMockGenerator(inputModelTypes: [result, response]); + + var providers = CodeModelGenerator.Instance.OutputLibrary.TypeProviders.OfType(); + + Assert.AreEqual("UnknownFooResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + Assert.AreEqual(1, providers.Count(provider => provider.Name == "UnknownFooResult")); + } + + [Test] + public async Task TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName() + { + var response = InputFactory.Model("FooResponse"); + var result = InputFactory.Model("FooResult"); + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [response, result], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync( + parameters: "LastContract")); + + Assert.AreEqual("FooResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + Assert.AreEqual("CustomFooResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(result)!.Name); + } + [TestCase(false)] [TestCase(true)] public async Task TestBuildName_ResponseSuffixAvoidsResultCustomizationAliasCollision(bool reverseOrder) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName(LastContract)/FooResult.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName(LastContract)/FooResult.cs new file mode 100644 index 00000000000..34fb79363fb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName(LastContract)/FooResult.cs @@ -0,0 +1,6 @@ +namespace Sample.Models +{ + public partial class FooResult + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName/CustomFooResult.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName/CustomFooResult.cs new file mode 100644 index 00000000000..3d129838744 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName/CustomFooResult.cs @@ -0,0 +1,9 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("FooResult")] + public partial class CustomFooResult + { + } +} From 30f7b3fb5643f343e42e410c14ba3ebd40a84f03 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 20:53:48 +0000 Subject: [PATCH 30/68] fix(csharp): prevent physical model name collisions Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 43 +++++++++++++++---- .../ModelProviders/ModelProviderTests.cs | 18 ++++++++ .../CustomWidget.cs | 9 ++++ 3 files changed, 61 insertions(+), 9 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsCustomizedPhysicalNameCollision/CustomWidget.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 6feffc50410..c34d30a782f 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 @@ -332,18 +332,25 @@ private protected override string NormalizeTypeName(string name) } var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; + var inputLibrary = CodeModelGenerator.Instance.InputLibrary; var customType = sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, resultName, DeclaringTypeName); - return (customType is not null && string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase)) || - HasConflictingName(CodeModelGenerator.Instance.InputLibrary, typeNamespace, resultName) + return (customType is not null && + (string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase) || + HasConflictingName(inputLibrary, customType.Type.Namespace, customType.Name, customType))) || + HasConflictingName(inputLibrary, typeNamespace, resultName) ? normalizedName : resultName; } // Model and enum files share a flat output directory, even across namespaces. - private bool HasConflictingName(InputLibrary inputLibrary, string typeNamespace, string resultName) - => GetEmittedModels(inputLibrary).Any(model => HasConflictingName(model, model.Namespace, resultName)) || - inputLibrary.InputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName)) || - inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName)); + private bool HasConflictingName( + InputLibrary inputLibrary, + string typeNamespace, + string resultName, + TypeProvider? excludedCustomization = null) + => GetEmittedModels(inputLibrary).Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || + inputLibrary.InputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || + inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); private static IEnumerable GetEmittedModels(InputLibrary inputLibrary) { @@ -359,7 +366,11 @@ private static IEnumerable GetEmittedModels(InputLibrary inputLi } } - private bool HasConflictingName(InputType inputType, string inputTypeNamespace, string resultName) + private bool HasConflictingName( + InputType inputType, + string inputTypeNamespace, + string resultName, + TypeProvider? excludedCustomization) { if (inputType == _inputModel) { @@ -369,7 +380,7 @@ private bool HasConflictingName(InputType inputType, string inputTypeNamespace, var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); var otherNamespace = GetTypeNamespace(inputTypeNamespace); var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); - if (customType is not null) + if (customType is not null && !IsSameCustomization(customType, excludedCustomization)) { return HasLastContractName(otherNamespace, resultName) || string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); @@ -418,15 +429,29 @@ private static int CompareModelIdentity(InputModelType left, InputModelType righ : string.Compare(left.CrossLanguageDefinitionId, right.CrossLanguageDefinitionId, StringComparison.Ordinal); } - private bool HasConflictingName(InputClient client, string typeNamespace, string resultName) + private bool HasConflictingName( + InputClient client, + string typeNamespace, + string resultName, + TypeProvider? excludedCustomization) { var clientNamespace = GetTypeNamespace(client.Namespace); var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); var customType = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); + if (IsSameCustomization(customType, excludedCustomization)) + { + customType = null; + } + return (customType?.Type.Namespace ?? clientNamespace) == typeNamespace && (customType?.Name ?? clientName) == resultName; } + private static bool IsSameCustomization(TypeProvider? left, TypeProvider? right) + => left is NamedTypeSymbolProvider leftSymbol && + right is NamedTypeSymbolProvider rightSymbol && + leftSymbol.MetadataName == rightSymbol.MetadataName; + private TypeProvider? FindCustomizationType( string typeNamespace, IEnumerable lookupNames) 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 1354558786c..eec64c15ef5 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 @@ -382,6 +382,24 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.AreEqual("CustomWidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(result)!.Name); } + [TestCase("model")] + [TestCase("enum")] + [TestCase("client")] + public async Task TestBuildName_ResponseSuffixAvoidsCustomizedPhysicalNameCollision(string typeKind) + { + var response = InputFactory.Model("WidgetResponse"); + var model = InputFactory.Model("CustomWidget"); + var @enum = InputFactory.StringEnum("CustomWidget", [("Value", "value")]); + var client = InputFactory.Client("CustomWidget", clientNamespace: "Sample.Models"); + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: typeKind == "model" ? [response, model] : [response], + inputEnumTypes: typeKind == "enum" ? [@enum] : [], + inputClients: typeKind == "client" ? [client] : [], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + Assert.AreEqual("WidgetResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + } + [Test] public void TestBuildName_ResponseSuffixAvoidsUnknownDiscriminatorCollision() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsCustomizedPhysicalNameCollision/CustomWidget.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsCustomizedPhysicalNameCollision/CustomWidget.cs new file mode 100644 index 00000000000..2b03b11a8ca --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsCustomizedPhysicalNameCollision/CustomWidget.cs @@ -0,0 +1,9 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("WidgetResult")] + public partial class CustomWidget + { + } +} From 4eafe268b0a12c9ff54092dbab50f7e33ad211fa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:22:05 +0000 Subject: [PATCH 31/68] fix(csharp): match emitted model/enum criteria in Result collision scan Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 25 +++++++- .../src/TypeFactory.cs | 10 ++++ .../ModelProviders/ModelProviderTests.cs | 58 +++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) 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 c34d30a782f..36a57e44a08 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 @@ -349,23 +349,44 @@ private bool HasConflictingName( string resultName, TypeProvider? excludedCustomization = null) => GetEmittedModels(inputLibrary).Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || - inputLibrary.InputNamespace.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || + GetEmittedEnums(inputLibrary).Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); private static IEnumerable GetEmittedModels(InputLibrary inputLibrary) { foreach (var model in inputLibrary.InputNamespace.Models) { + if (!IsEmittedModel(model)) + { + continue; + } + yield return model; var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(model => model.IsUnknownDiscriminatorModel); - if (unknownVariant is not null) + if (unknownVariant is not null && IsEmittedModel(unknownVariant)) { yield return unknownVariant; } } } + // Mirrors OutputLibrary.BuildModels: a model resolved to a framework/referenced type + // (via `external`) is represented by a SystemObjectModelProvider and never emitted as a + // generated file, so it cannot participate in a filename collision. This intentionally + // avoids TypeFactory.CreateModel/TypeProvider.Type here: those force construction of the + // sibling's provider (and, transitively, its own name/collision checks) while this + // provider's own name is still being built, which can re-enter the same lazy state. + private static bool IsEmittedModel(InputModelType model) + => model.External is null || !CodeModelGenerator.Instance.TypeFactory.IsResolvedExternalModel(model); + + // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and an enum + // marked external always maps to an existing framework/referenced type instead of being + // generated (unconditionally, unlike models), so neither can collide with an emitted name. + private static IEnumerable GetEmittedEnums(InputLibrary inputLibrary) + => inputLibrary.InputNamespace.Enums.Where( + @enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)); + private bool HasConflictingName( InputType inputType, string inputTypeNamespace, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index 17dcbd3808f..b4a45d95474 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -237,6 +237,16 @@ protected internal TypeFactory() return externalType != null ? new SystemObjectModelProvider(externalType, model) : null; } + /// + /// Determines whether is marked external (via @alternateType) and its + /// target type resolves, meaning it is represented by a rather + /// than emitted as a generated file (mirroring ). Unlike + /// , this performs no provider construction or + /// access, so it is safe to call while another provider's name is still being built. + /// + internal bool IsResolvedExternalModel(InputModelType model) + => model.External != null && CreateExternalType(model.External) != null; + /// /// Factory method for creating the that emits the /// generated ModelFactory for the current output library. 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 eec64c15ef5..a88f1e0b180 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 @@ -341,6 +341,64 @@ public void TestBuildName_ResponseSuffixAvoidsEnumCollision(bool isExtensible) Assert.AreEqual("IPResult", CodeModelGenerator.Instance.TypeFactory.CreateEnum(result)!.Name); } + [Test] + public void TestBuildName_ResponseSuffixIgnoresApiVersionEnumCollision() + { + var response = InputFactory.Model("WidgetResponse"); + var apiVersionEnum = InputFactory.StringEnum( + "WidgetResult", + [("Value", "value")], + usage: InputModelTypeUsage.ApiVersionEnum); + MockHelpers.LoadMockGenerator(inputModelTypes: [response], inputEnumTypes: [apiVersionEnum]); + + // The API-version enum is never emitted, so it cannot reserve the "WidgetResult" name. + Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + } + + [Test] + public void TestBuildName_ResponseSuffixIgnoresExternalEnumCollision() + { + var response = InputFactory.Model("WidgetResponse"); + var externalEnum = InputFactory.StringEnum( + "WidgetResult", + [("Value", "value")], + external: new InputExternalTypeMetadata("System.Net.HttpStatusCode", null, null)); + MockHelpers.LoadMockGenerator(inputModelTypes: [response], inputEnumTypes: [externalEnum]); + + // An external enum always maps to an existing type instead of being generated, so it + // cannot reserve the "WidgetResult" name. + Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + } + + [Test] + public void TestBuildName_ResponseSuffixIgnoresResolvedExternalModelCollision() + { + var response = InputFactory.Model("WidgetResponse"); + var externalModel = InputFactory.Model( + "WidgetResult", + external: new InputExternalTypeMetadata("System.Exception", null, null)); + MockHelpers.LoadMockGenerator(inputModelTypes: [response, externalModel]); + + // The external model resolves to an existing framework type, so it is represented by a + // SystemObjectModelProvider and never emitted as a generated file; it cannot reserve + // the "WidgetResult" name. + Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + } + + [Test] + public void TestBuildName_ResponseSuffixHonorsUnresolvedExternalModelCollision() + { + var response = InputFactory.Model("WidgetResponse"); + var externalModel = InputFactory.Model( + "WidgetResult", + external: new InputExternalTypeMetadata("Some.Unresolvable.ExternalType", null, null)); + MockHelpers.LoadMockGenerator(inputModelTypes: [response, externalModel]); + + // The external type cannot be resolved, so the model falls back to normal generation + // and still reserves the "WidgetResult" name. + Assert.AreEqual("WidgetResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + } + [TestCase("OtherModel")] [TestCase("WidgetResult")] public async Task TestBuildName_ResponseSuffixAvoidsCustomizationCollision(string otherName) From d4cd999b23f6b74bbb4ab53ef603e9e5665003b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:44:22 +0000 Subject: [PATCH 32/68] refactor(csharp): cache emitted naming inventory Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 37 +++++++++---------- .../src/TypeFactory.cs | 10 ----- 2 files changed, 17 insertions(+), 30 deletions(-) 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 36a57e44a08..7104b510c79 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 @@ -75,6 +75,8 @@ protected override FormattableString BuildDescription() internal PropertyProvider? DiscriminatorProperty { get; private set; } private readonly bool _isDiscriminatedBaseType; + private IReadOnlyList? _emittedModels; + private IReadOnlyList? _emittedEnums; private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -349,14 +351,23 @@ private bool HasConflictingName( string resultName, TypeProvider? excludedCustomization = null) => GetEmittedModels(inputLibrary).Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || - GetEmittedEnums(inputLibrary).Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || + EmittedEnums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); - private static IEnumerable GetEmittedModels(InputLibrary inputLibrary) + private IReadOnlyList EmittedEnums + => _emittedEnums ??= CodeModelGenerator.Instance.InputLibrary.InputNamespace.Enums + .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) + .ToList(); + + private IReadOnlyList GetEmittedModels(InputLibrary inputLibrary) + => _emittedModels ??= BuildEmittedModels(inputLibrary).ToList(); + + private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary) { foreach (var model in inputLibrary.InputNamespace.Models) { - if (!IsEmittedModel(model)) + if (model.External is not null && + CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(model) is not null) { continue; } @@ -364,29 +375,15 @@ private static IEnumerable GetEmittedModels(InputLibrary inputLi yield return model; var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(model => model.IsUnknownDiscriminatorModel); - if (unknownVariant is not null && IsEmittedModel(unknownVariant)) + if (unknownVariant is not null && + (unknownVariant.External is null || + CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(unknownVariant) is null)) { yield return unknownVariant; } } } - // Mirrors OutputLibrary.BuildModels: a model resolved to a framework/referenced type - // (via `external`) is represented by a SystemObjectModelProvider and never emitted as a - // generated file, so it cannot participate in a filename collision. This intentionally - // avoids TypeFactory.CreateModel/TypeProvider.Type here: those force construction of the - // sibling's provider (and, transitively, its own name/collision checks) while this - // provider's own name is still being built, which can re-enter the same lazy state. - private static bool IsEmittedModel(InputModelType model) - => model.External is null || !CodeModelGenerator.Instance.TypeFactory.IsResolvedExternalModel(model); - - // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and an enum - // marked external always maps to an existing framework/referenced type instead of being - // generated (unconditionally, unlike models), so neither can collide with an emitted name. - private static IEnumerable GetEmittedEnums(InputLibrary inputLibrary) - => inputLibrary.InputNamespace.Enums.Where( - @enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)); - private bool HasConflictingName( InputType inputType, string inputTypeNamespace, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index b4a45d95474..17dcbd3808f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -237,16 +237,6 @@ protected internal TypeFactory() return externalType != null ? new SystemObjectModelProvider(externalType, model) : null; } - /// - /// Determines whether is marked external (via @alternateType) and its - /// target type resolves, meaning it is represented by a rather - /// than emitted as a generated file (mirroring ). Unlike - /// , this performs no provider construction or - /// access, so it is safe to call while another provider's name is still being built. - /// - internal bool IsResolvedExternalModel(InputModelType model) - => model.External != null && CreateExternalType(model.External) != null; - /// /// Factory method for creating the that emits the /// generated ModelFactory for the current output library. From 1402733f0b0560a3822b21cb84b6c83747ce5755 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:47:33 +0000 Subject: [PATCH 33/68] refactor(csharp): share emitted type collision cache Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 37 +++++++++++-------- .../src/TypeFactory.cs | 2 +- 2 files changed, 23 insertions(+), 16 deletions(-) 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 7104b510c79..651a45764c1 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 @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; @@ -75,8 +76,7 @@ protected override FormattableString BuildDescription() internal PropertyProvider? DiscriminatorProperty { get; private set; } private readonly bool _isDiscriminatedBaseType; - private IReadOnlyList? _emittedModels; - private IReadOnlyList? _emittedEnums; + private static readonly ConditionalWeakTable _emittedTypesCache = new(); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -350,24 +350,28 @@ private bool HasConflictingName( string typeNamespace, string resultName, TypeProvider? excludedCustomization = null) - => GetEmittedModels(inputLibrary).Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || - EmittedEnums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || + { + var emittedTypes = GetEmittedTypes(inputLibrary); + return emittedTypes.Models.Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || + emittedTypes.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); + } - private IReadOnlyList EmittedEnums - => _emittedEnums ??= CodeModelGenerator.Instance.InputLibrary.InputNamespace.Enums - .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - .ToList(); - - private IReadOnlyList GetEmittedModels(InputLibrary inputLibrary) - => _emittedModels ??= BuildEmittedModels(inputLibrary).ToList(); + private static EmittedTypes GetEmittedTypes(InputLibrary inputLibrary) + => _emittedTypesCache.GetValue( + inputLibrary, + static inputLibrary => new( + BuildEmittedModels(inputLibrary).ToList(), + inputLibrary.InputNamespace.Enums + .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) + .ToList())); private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary) { foreach (var model in inputLibrary.InputNamespace.Models) { if (model.External is not null && - CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(model) is not null) + CodeModelGenerator.Instance.TypeFactory.CreateExternalType(model.External) is not null) { continue; } @@ -377,13 +381,15 @@ private static IEnumerable BuildEmittedModels(InputLibrary input var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(model => model.IsUnknownDiscriminatorModel); if (unknownVariant is not null && (unknownVariant.External is null || - CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(unknownVariant) is null)) + CodeModelGenerator.Instance.TypeFactory.CreateExternalType(unknownVariant.External) is null)) { yield return unknownVariant; } } } + private sealed record EmittedTypes(IReadOnlyList Models, IReadOnlyList Enums); + private bool HasConflictingName( InputType inputType, string inputTypeNamespace, @@ -546,9 +552,10 @@ private bool HasCustomizedSiblingInputName(string typeName) } var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - foreach (var (inputType, inputTypeNamespace) in GetEmittedModels(CodeModelGenerator.Instance.InputLibrary) + var emittedTypes = GetEmittedTypes(CodeModelGenerator.Instance.InputLibrary); + foreach (var (inputType, inputTypeNamespace) in emittedTypes.Models .Select(model => ((InputType)model, model.Namespace)) - .Concat(inputNamespace.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) + .Concat(emittedTypes.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) { if (inputType == _inputModel) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index 17dcbd3808f..b11052aa318 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -331,7 +331,7 @@ protected virtual ModelFactoryProvider CreateModelFactoryCore(IEnumerableThe originating , when available, used to preserve /// semantics (such as extensible-enum backing) that reflection alone cannot recover from the resolved type. /// A representing the external type, or null if the type cannot be resolved. - private CSharpType? CreateExternalType(InputExternalTypeMetadata externalProperties, InputType? inputType = null) + internal CSharpType? CreateExternalType(InputExternalTypeMetadata externalProperties, InputType? inputType = null) { // Resolve the type: first as a framework type from the fully qualified name (free, no I/O, and the // source of truth for BCL types), then, on a miss, dynamically from the NuGet package named in the From 05c7fd406f0a1a21cb49de1b241d5668db6dbc9d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:49:35 +0000 Subject: [PATCH 34/68] docs(csharp): clarify emitted collision cache rules Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) 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 651a45764c1..b0087eceef1 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 @@ -76,6 +76,8 @@ protected override FormattableString BuildDescription() internal PropertyProvider? DiscriminatorProperty { get; private set; } private readonly bool _isDiscriminatedBaseType; + // The input library is fixed before providers are named. Cache the emitted collision inventory + // once per library instead of rebuilding it for every ModelProvider that checks a Response->Result name. private static readonly ConditionalWeakTable _emittedTypesCache = new(); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -363,9 +365,17 @@ private static EmittedTypes GetEmittedTypes(InputLibrary inputLibrary) static inputLibrary => new( BuildEmittedModels(inputLibrary).ToList(), inputLibrary.InputNamespace.Enums + // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external + // enums always map to existing types instead of generated files. .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) .ToList())); + // Mirrors OutputLibrary.BuildModels: a model resolved to a framework/referenced type + // (via `external`) is represented by a SystemObjectModelProvider and never emitted as a + // generated file, so it cannot participate in a filename collision. This intentionally + // uses TypeFactory.CreateExternalType instead of TypeFactory.CreateModel/TypeProvider.Type: + // constructing a sibling provider here can re-enter name/collision checks while this + // provider's own name is still being built. private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary) { foreach (var model in inputLibrary.InputNamespace.Models) @@ -379,12 +389,18 @@ private static IEnumerable BuildEmittedModels(InputLibrary input yield return model; var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(model => model.IsUnknownDiscriminatorModel); - if (unknownVariant is not null && - (unknownVariant.External is null || - CodeModelGenerator.Instance.TypeFactory.CreateExternalType(unknownVariant.External) is null)) + if (unknownVariant is null) { - yield return unknownVariant; + continue; + } + + if (unknownVariant.External is not null && + CodeModelGenerator.Instance.TypeFactory.CreateExternalType(unknownVariant.External) is not null) + { + continue; } + + yield return unknownVariant; } } From 11ec3fbc80455f636cce34ce0189b56b07145049 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:51:25 +0000 Subject: [PATCH 35/68] refactor(csharp): preserve customization enum scan Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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 b0087eceef1..8840d85421e 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 @@ -380,8 +380,9 @@ private static IEnumerable BuildEmittedModels(InputLibrary input { foreach (var model in inputLibrary.InputNamespace.Models) { - if (model.External is not null && - CodeModelGenerator.Instance.TypeFactory.CreateExternalType(model.External) is not null) + var external = model.External; + if (external is not null && + CodeModelGenerator.Instance.TypeFactory.CreateExternalType(external) is not null) { continue; } @@ -394,8 +395,9 @@ private static IEnumerable BuildEmittedModels(InputLibrary input continue; } - if (unknownVariant.External is not null && - CodeModelGenerator.Instance.TypeFactory.CreateExternalType(unknownVariant.External) is not null) + external = unknownVariant.External; + if (external is not null && + CodeModelGenerator.Instance.TypeFactory.CreateExternalType(external) is not null) { continue; } @@ -571,7 +573,7 @@ private bool HasCustomizedSiblingInputName(string typeName) var emittedTypes = GetEmittedTypes(CodeModelGenerator.Instance.InputLibrary); foreach (var (inputType, inputTypeNamespace) in emittedTypes.Models .Select(model => ((InputType)model, model.Namespace)) - .Concat(emittedTypes.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) + .Concat(inputNamespace.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) { if (inputType == _inputModel) { From 30761ce6f91b1146a4eeb9740ebaa80963938022 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:53:15 +0000 Subject: [PATCH 36/68] refactor(csharp): clarify emitted model predicate Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 8840d85421e..911b20d9c0e 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 @@ -380,9 +380,7 @@ private static IEnumerable BuildEmittedModels(InputLibrary input { foreach (var model in inputLibrary.InputNamespace.Models) { - var external = model.External; - if (external is not null && - CodeModelGenerator.Instance.TypeFactory.CreateExternalType(external) is not null) + if (!IsEmitted(model)) { continue; } @@ -395,15 +393,17 @@ private static IEnumerable BuildEmittedModels(InputLibrary input continue; } - external = unknownVariant.External; - if (external is not null && - CodeModelGenerator.Instance.TypeFactory.CreateExternalType(external) is not null) + if (!IsEmitted(unknownVariant)) { continue; } yield return unknownVariant; } + + static bool IsEmitted(InputModelType model) + => model.External is null || + CodeModelGenerator.Instance.TypeFactory.CreateExternalType(model.External) is null; } private sealed record EmittedTypes(IReadOnlyList Models, IReadOnlyList Enums); @@ -573,6 +573,8 @@ private bool HasCustomizedSiblingInputName(string typeName) var emittedTypes = GetEmittedTypes(CodeModelGenerator.Instance.InputLibrary); foreach (var (inputType, inputTypeNamespace) in emittedTypes.Models .Select(model => ((InputType)model, model.Namespace)) + // Keep the customization guard on the full enum set. Custom code can still exist for + // non-emitted enums and should continue to reserve its physical CLR name. .Concat(inputNamespace.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) { if (inputType == _inputModel) From 7bb4ae4164d151649bb09d1562178697c13cd32f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:54:56 +0000 Subject: [PATCH 37/68] refactor(csharp): guard emitted type cache Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) 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 911b20d9c0e..c9c8c7e4846 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 @@ -79,6 +79,7 @@ protected override FormattableString BuildDescription() // The input library is fixed before providers are named. Cache the emitted collision inventory // once per library instead of rebuilding it for every ModelProvider that checks a Response->Result name. private static readonly ConditionalWeakTable _emittedTypesCache = new(); + private static readonly object _emittedTypesCacheLock = new(); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -360,15 +361,30 @@ private bool HasConflictingName( } private static EmittedTypes GetEmittedTypes(InputLibrary inputLibrary) - => _emittedTypesCache.GetValue( - inputLibrary, - static inputLibrary => new( + { + if (_emittedTypesCache.TryGetValue(inputLibrary, out var emittedTypes)) + { + return emittedTypes; + } + + lock (_emittedTypesCacheLock) + { + if (_emittedTypesCache.TryGetValue(inputLibrary, out emittedTypes)) + { + return emittedTypes; + } + + emittedTypes = new( BuildEmittedModels(inputLibrary).ToList(), inputLibrary.InputNamespace.Enums // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external // enums always map to existing types instead of generated files. .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - .ToList())); + .ToList()); + _emittedTypesCache.Add(inputLibrary, emittedTypes); + return emittedTypes; + } + } // Mirrors OutputLibrary.BuildModels: a model resolved to a framework/referenced type // (via `external`) is represented by a SystemObjectModelProvider and never emitted as a @@ -401,9 +417,9 @@ private static IEnumerable BuildEmittedModels(InputLibrary input yield return unknownVariant; } - static bool IsEmitted(InputModelType model) - => model.External is null || - CodeModelGenerator.Instance.TypeFactory.CreateExternalType(model.External) is null; + static bool IsEmitted(InputModelType candidate) + => candidate.External is null || + CodeModelGenerator.Instance.TypeFactory.CreateExternalType(candidate.External) is null; } private sealed record EmittedTypes(IReadOnlyList Models, IReadOnlyList Enums); From b68d0203709759e66176a8a820f0cf6aec12b3f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:57:22 +0000 Subject: [PATCH 38/68] test(csharp): cover repeated emitted enum checks Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 51 +++++++------------ .../ModelProviders/ModelProviderTests.cs | 10 +++- 2 files changed, 27 insertions(+), 34 deletions(-) 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 c9c8c7e4846..a7cb571dce1 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 @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; @@ -78,8 +79,7 @@ protected override FormattableString BuildDescription() private readonly bool _isDiscriminatedBaseType; // The input library is fixed before providers are named. Cache the emitted collision inventory // once per library instead of rebuilding it for every ModelProvider that checks a Response->Result name. - private static readonly ConditionalWeakTable _emittedTypesCache = new(); - private static readonly object _emittedTypesCacheLock = new(); + private static readonly ConditionalWeakTable> _emittedTypesCache = new(); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -361,37 +361,18 @@ private bool HasConflictingName( } private static EmittedTypes GetEmittedTypes(InputLibrary inputLibrary) - { - if (_emittedTypesCache.TryGetValue(inputLibrary, out var emittedTypes)) - { - return emittedTypes; - } - - lock (_emittedTypesCacheLock) - { - if (_emittedTypesCache.TryGetValue(inputLibrary, out emittedTypes)) - { - return emittedTypes; - } - - emittedTypes = new( - BuildEmittedModels(inputLibrary).ToList(), - inputLibrary.InputNamespace.Enums - // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external - // enums always map to existing types instead of generated files. - .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - .ToList()); - _emittedTypesCache.Add(inputLibrary, emittedTypes); - return emittedTypes; - } - } + => _emittedTypesCache.GetValue( + inputLibrary, + static inputLibrary => new( + () => new EmittedTypes( + BuildEmittedModels(inputLibrary).ToList(), + inputLibrary.InputNamespace.Enums + // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external + // enums always map to existing types instead of generated files. + .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) + .ToList()), + LazyThreadSafetyMode.ExecutionAndPublication)).Value; - // Mirrors OutputLibrary.BuildModels: a model resolved to a framework/referenced type - // (via `external`) is represented by a SystemObjectModelProvider and never emitted as a - // generated file, so it cannot participate in a filename collision. This intentionally - // uses TypeFactory.CreateExternalType instead of TypeFactory.CreateModel/TypeProvider.Type: - // constructing a sibling provider here can re-enter name/collision checks while this - // provider's own name is still being built. private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary) { foreach (var model in inputLibrary.InputNamespace.Models) @@ -417,6 +398,12 @@ private static IEnumerable BuildEmittedModels(InputLibrary input yield return unknownVariant; } + // Mirrors OutputLibrary.BuildModels: a model resolved to a framework/referenced type + // (via `external`) is represented by a SystemObjectModelProvider and never emitted as a + // generated file, so it cannot participate in a filename collision. This intentionally + // uses TypeFactory.CreateExternalType instead of TypeFactory.CreateModel/TypeProvider.Type: + // constructing a sibling provider here can re-enter name/collision checks while this + // provider's own name is still being built. static bool IsEmitted(InputModelType candidate) => candidate.External is null || CodeModelGenerator.Instance.TypeFactory.CreateExternalType(candidate.External) is null; 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 a88f1e0b180..09dd86f1c4f 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 @@ -352,7 +352,10 @@ public void TestBuildName_ResponseSuffixIgnoresApiVersionEnumCollision() MockHelpers.LoadMockGenerator(inputModelTypes: [response], inputEnumTypes: [apiVersionEnum]); // The API-version enum is never emitted, so it cannot reserve the "WidgetResult" name. - Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + var provider = CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!; + Assert.AreEqual("WidgetResult", provider.Name); + provider.Reset(); + Assert.AreEqual("WidgetResult", provider.Name); } [Test] @@ -367,7 +370,10 @@ public void TestBuildName_ResponseSuffixIgnoresExternalEnumCollision() // An external enum always maps to an existing type instead of being generated, so it // cannot reserve the "WidgetResult" name. - Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); + var provider = CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!; + Assert.AreEqual("WidgetResult", provider.Name); + provider.Reset(); + Assert.AreEqual("WidgetResult", provider.Name); } [Test] From d296398512618911e4b6e67288537e9028ae8351 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:59:01 +0000 Subject: [PATCH 39/68] refactor(csharp): simplify unknown variant cache scan Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) 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 a7cb571dce1..d618028ffd0 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 @@ -385,17 +385,10 @@ private static IEnumerable BuildEmittedModels(InputLibrary input yield return model; var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(model => model.IsUnknownDiscriminatorModel); - if (unknownVariant is null) + if (unknownVariant is not null && IsEmitted(unknownVariant)) { - continue; - } - - if (!IsEmitted(unknownVariant)) - { - continue; + yield return unknownVariant; } - - yield return unknownVariant; } // Mirrors OutputLibrary.BuildModels: a model resolved to a framework/referenced type From 1d1fb59016ed00f887b220be41f3191026886946 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:01:55 +0000 Subject: [PATCH 40/68] refactor(csharp): key emitted cache by generator Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) 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 d618028ffd0..0575ea5432b 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 @@ -79,7 +79,7 @@ protected override FormattableString BuildDescription() private readonly bool _isDiscriminatedBaseType; // The input library is fixed before providers are named. Cache the emitted collision inventory // once per library instead of rebuilding it for every ModelProvider that checks a Response->Result name. - private static readonly ConditionalWeakTable> _emittedTypesCache = new(); + private static readonly ConditionalWeakTable> _emittedTypesCache = new(); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -354,26 +354,26 @@ private bool HasConflictingName( string resultName, TypeProvider? excludedCustomization = null) { - var emittedTypes = GetEmittedTypes(inputLibrary); + var emittedTypes = GetEmittedTypes(); return emittedTypes.Models.Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || emittedTypes.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); } - private static EmittedTypes GetEmittedTypes(InputLibrary inputLibrary) + private static EmittedTypes GetEmittedTypes() => _emittedTypesCache.GetValue( - inputLibrary, - static inputLibrary => new( + CodeModelGenerator.Instance, + static generator => new( () => new EmittedTypes( - BuildEmittedModels(inputLibrary).ToList(), - inputLibrary.InputNamespace.Enums + BuildEmittedModels(generator.InputLibrary, generator.TypeFactory).ToList(), + generator.InputLibrary.InputNamespace.Enums // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external // enums always map to existing types instead of generated files. .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) .ToList()), LazyThreadSafetyMode.ExecutionAndPublication)).Value; - private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary) + private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary, TypeFactory typeFactory) { foreach (var model in inputLibrary.InputNamespace.Models) { @@ -397,9 +397,9 @@ private static IEnumerable BuildEmittedModels(InputLibrary input // uses TypeFactory.CreateExternalType instead of TypeFactory.CreateModel/TypeProvider.Type: // constructing a sibling provider here can re-enter name/collision checks while this // provider's own name is still being built. - static bool IsEmitted(InputModelType candidate) + bool IsEmitted(InputModelType candidate) => candidate.External is null || - CodeModelGenerator.Instance.TypeFactory.CreateExternalType(candidate.External) is null; + typeFactory.CreateExternalType(candidate.External) is null; } private sealed record EmittedTypes(IReadOnlyList Models, IReadOnlyList Enums); @@ -566,11 +566,12 @@ private bool HasCustomizedSiblingInputName(string typeName) } var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - var emittedTypes = GetEmittedTypes(CodeModelGenerator.Instance.InputLibrary); + var emittedTypes = GetEmittedTypes(); foreach (var (inputType, inputTypeNamespace) in emittedTypes.Models .Select(model => ((InputType)model, model.Namespace)) - // Keep the customization guard on the full enum set. Custom code can still exist for - // non-emitted enums and should continue to reserve its physical CLR name. + // Models use the emitted inventory because resolved external models do not produce + // generated model providers/files. Keep enums on the full set because custom code can + // still exist for non-emitted enums and should continue to reserve its physical CLR name. .Concat(inputNamespace.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) { if (inputType == _inputModel) From 3b8a98edef62e1535f0e20bd59e496afcc3948fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 21 Sep 2026 23:03:42 +0000 Subject: [PATCH 41/68] refactor(csharp): avoid retaining generator cache keys Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) 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 0575ea5432b..f6d8f48ea9a 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 @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using System.Threading; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; @@ -79,7 +78,7 @@ protected override FormattableString BuildDescription() private readonly bool _isDiscriminatedBaseType; // The input library is fixed before providers are named. Cache the emitted collision inventory // once per library instead of rebuilding it for every ModelProvider that checks a Response->Result name. - private static readonly ConditionalWeakTable> _emittedTypesCache = new(); + private static readonly ConditionalWeakTable _emittedTypesCache = new(); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -364,14 +363,12 @@ private static EmittedTypes GetEmittedTypes() => _emittedTypesCache.GetValue( CodeModelGenerator.Instance, static generator => new( - () => new EmittedTypes( - BuildEmittedModels(generator.InputLibrary, generator.TypeFactory).ToList(), - generator.InputLibrary.InputNamespace.Enums - // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external - // enums always map to existing types instead of generated files. - .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - .ToList()), - LazyThreadSafetyMode.ExecutionAndPublication)).Value; + BuildEmittedModels(generator.InputLibrary, generator.TypeFactory).ToList(), + generator.InputLibrary.InputNamespace.Enums + // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external + // enums always map to existing types instead of generated files. + .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) + .ToList())); private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary, TypeFactory typeFactory) { From a04613e84352470924dc70ff209279f8c2a7ecfb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:06:50 +0000 Subject: [PATCH 42/68] fix(csharp): reserve resolved external result names Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 44 +++++++++++++++++-- .../src/TypeFactory.cs | 7 ++- .../ModelProviders/ModelProviderTests.cs | 35 +++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) 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 f6d8f48ea9a..3230924918e 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 @@ -356,6 +356,7 @@ private bool HasConflictingName( var emittedTypes = GetEmittedTypes(); return emittedTypes.Models.Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || emittedTypes.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || + emittedTypes.ExternalTypes.Any(type => HasConflictingName(type, typeNamespace, resultName)) || inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); } @@ -368,7 +369,8 @@ private static EmittedTypes GetEmittedTypes() // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external // enums always map to existing types instead of generated files. .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - .ToList())); + .ToList(), + BuildResolvedExternalTypes(generator.InputLibrary, generator.TypeFactory).ToList())); private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary, TypeFactory typeFactory) { @@ -396,10 +398,46 @@ private static IEnumerable BuildEmittedModels(InputLibrary input // provider's own name is still being built. bool IsEmitted(InputModelType candidate) => candidate.External is null || - typeFactory.CreateExternalType(candidate.External) is null; + typeFactory.CreateExternalType(candidate.External, reportDiagnostic: false) is null; } - private sealed record EmittedTypes(IReadOnlyList Models, IReadOnlyList Enums); + private static IEnumerable BuildResolvedExternalTypes(InputLibrary inputLibrary, TypeFactory typeFactory) + { + foreach (var model in inputLibrary.InputNamespace.Models) + { + if (model.External is not null && + typeFactory.CreateExternalType(model.External, reportDiagnostic: false) is { } modelType) + { + yield return modelType; + } + + var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(model => model.IsUnknownDiscriminatorModel); + if (unknownVariant?.External is not null && + typeFactory.CreateExternalType(unknownVariant.External, reportDiagnostic: false) is { } unknownVariantType) + { + yield return unknownVariantType; + } + } + + foreach (var @enum in inputLibrary.InputNamespace.Enums) + { + if (@enum.External is not null && + typeFactory.CreateExternalType(@enum.External, @enum, reportDiagnostic: false) is { } enumType) + { + yield return enumType; + } + } + } + + private static bool HasConflictingName(CSharpType externalType, string typeNamespace, string resultName) + => externalType.DeclaringType is null && + string.Equals(externalType.Namespace, typeNamespace, StringComparison.Ordinal) && + string.Equals(externalType.Name, resultName, StringComparison.Ordinal); + + private sealed record EmittedTypes( + IReadOnlyList Models, + IReadOnlyList Enums, + IReadOnlyList ExternalTypes); private bool HasConflictingName( InputType inputType, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index b11052aa318..e0099b4c235 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -331,7 +331,7 @@ protected virtual ModelFactoryProvider CreateModelFactoryCore(IEnumerableThe originating , when available, used to preserve /// semantics (such as extensible-enum backing) that reflection alone cannot recover from the resolved type. /// A representing the external type, or null if the type cannot be resolved. - internal CSharpType? CreateExternalType(InputExternalTypeMetadata externalProperties, InputType? inputType = null) + internal CSharpType? CreateExternalType(InputExternalTypeMetadata externalProperties, InputType? inputType = null, bool reportDiagnostic = true) { // Resolve the type: first as a framework type from the fully qualified name (free, no I/O, and the // source of truth for BCL types), then, on a miss, dynamically from the NuGet package named in the @@ -363,6 +363,11 @@ protected virtual ModelFactoryProvider CreateModelFactoryCore(IEnumerable Date: Tue, 22 Sep 2026 16:17:06 +0000 Subject: [PATCH 43/68] fix(csharp): ignore non-emitted enum customizations Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 5 +---- .../ModelProviders/ModelProviderTests.cs | 21 +++++++++++++++++++ .../CustomWidgetResult.cs | 9 ++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresNonEmittedEnumCustomizationSibling/CustomWidgetResult.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 3230924918e..45f4749c536 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 @@ -604,10 +604,7 @@ private bool HasCustomizedSiblingInputName(string typeName) var emittedTypes = GetEmittedTypes(); foreach (var (inputType, inputTypeNamespace) in emittedTypes.Models .Select(model => ((InputType)model, model.Namespace)) - // Models use the emitted inventory because resolved external models do not produce - // generated model providers/files. Keep enums on the full set because custom code can - // still exist for non-emitted enums and should continue to reserve its physical CLR name. - .Concat(inputNamespace.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) + .Concat(emittedTypes.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) { if (inputType == _inputModel) { 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 2c1abc05869..668edd7f755 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 @@ -249,6 +249,27 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.IsNotNull(model.CustomCodeView); } + [TestCase("apiVersion")] + [TestCase("external")] + public async Task TestBuildName_ResponseSuffixIgnoresNonEmittedEnumCustomizationSibling(string enumKind) + { + var response = InputFactory.Model("WidgetResponse"); + var nonEmittedEnum = InputFactory.StringEnum( + "WidgetResult", + [("Value", "value")], + usage: enumKind == "apiVersion" ? InputModelTypeUsage.ApiVersionEnum : InputModelTypeUsage.None, + external: enumKind == "external" ? new InputExternalTypeMetadata("System.Net.HttpStatusCode", null, null) : null); + await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [response], + inputEnumTypes: [nonEmittedEnum], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var model = CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!; + + Assert.AreEqual("CustomWidgetResult", model.Name); + Assert.AreEqual("CustomWidgetResult", model.CustomCodeView?.Name); + } + [Test] public async Task TestBuildName_ResponseSuffixPreservesCustomResponseRenameWithoutLastContract() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresNonEmittedEnumCustomizationSibling/CustomWidgetResult.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresNonEmittedEnumCustomizationSibling/CustomWidgetResult.cs new file mode 100644 index 00000000000..df1a4cdeee7 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresNonEmittedEnumCustomizationSibling/CustomWidgetResult.cs @@ -0,0 +1,9 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("WidgetResult")] + public partial class CustomWidgetResult + { + } +} From 5b677bf5c234d41d0677a80030ded34650cebed4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:37:48 +0000 Subject: [PATCH 44/68] refactor(csharp): simplify model name collision checks Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 91 +++---------------- .../src/TypeFactory.cs | 7 +- .../ModelProviders/ModelProviderTests.cs | 16 ++-- 3 files changed, 22 insertions(+), 92 deletions(-) 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 45f4749c536..bb9448c6d84 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 @@ -356,7 +356,6 @@ private bool HasConflictingName( var emittedTypes = GetEmittedTypes(); return emittedTypes.Models.Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || emittedTypes.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || - emittedTypes.ExternalTypes.Any(type => HasConflictingName(type, typeNamespace, resultName)) || inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); } @@ -364,19 +363,18 @@ private static EmittedTypes GetEmittedTypes() => _emittedTypesCache.GetValue( CodeModelGenerator.Instance, static generator => new( - BuildEmittedModels(generator.InputLibrary, generator.TypeFactory).ToList(), + BuildEmittedModels(generator.InputLibrary).ToList(), generator.InputLibrary.InputNamespace.Enums // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external // enums always map to existing types instead of generated files. .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - .ToList(), - BuildResolvedExternalTypes(generator.InputLibrary, generator.TypeFactory).ToList())); + .ToList())); - private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary, TypeFactory typeFactory) + private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary) { foreach (var model in inputLibrary.InputNamespace.Models) { - if (!IsEmitted(model)) + if (model.External is not null) { continue; } @@ -384,60 +382,14 @@ private static IEnumerable BuildEmittedModels(InputLibrary input yield return model; var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(model => model.IsUnknownDiscriminatorModel); - if (unknownVariant is not null && IsEmitted(unknownVariant)) + if (unknownVariant is { External: null }) { yield return unknownVariant; } } - - // Mirrors OutputLibrary.BuildModels: a model resolved to a framework/referenced type - // (via `external`) is represented by a SystemObjectModelProvider and never emitted as a - // generated file, so it cannot participate in a filename collision. This intentionally - // uses TypeFactory.CreateExternalType instead of TypeFactory.CreateModel/TypeProvider.Type: - // constructing a sibling provider here can re-enter name/collision checks while this - // provider's own name is still being built. - bool IsEmitted(InputModelType candidate) - => candidate.External is null || - typeFactory.CreateExternalType(candidate.External, reportDiagnostic: false) is null; - } - - private static IEnumerable BuildResolvedExternalTypes(InputLibrary inputLibrary, TypeFactory typeFactory) - { - foreach (var model in inputLibrary.InputNamespace.Models) - { - if (model.External is not null && - typeFactory.CreateExternalType(model.External, reportDiagnostic: false) is { } modelType) - { - yield return modelType; - } - - var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(model => model.IsUnknownDiscriminatorModel); - if (unknownVariant?.External is not null && - typeFactory.CreateExternalType(unknownVariant.External, reportDiagnostic: false) is { } unknownVariantType) - { - yield return unknownVariantType; - } - } - - foreach (var @enum in inputLibrary.InputNamespace.Enums) - { - if (@enum.External is not null && - typeFactory.CreateExternalType(@enum.External, @enum, reportDiagnostic: false) is { } enumType) - { - yield return enumType; - } - } } - private static bool HasConflictingName(CSharpType externalType, string typeNamespace, string resultName) - => externalType.DeclaringType is null && - string.Equals(externalType.Namespace, typeNamespace, StringComparison.Ordinal) && - string.Equals(externalType.Name, resultName, StringComparison.Ordinal); - - private sealed record EmittedTypes( - IReadOnlyList Models, - IReadOnlyList Enums, - IReadOnlyList ExternalTypes); + private sealed record EmittedTypes(IReadOnlyList Models, IReadOnlyList Enums); private bool HasConflictingName( InputType inputType, @@ -465,32 +417,17 @@ private bool HasConflictingName( return true; } - if (inputType is not InputModelType otherModel || inputType.IsExactName) - { - return false; - } - var normalizedName = otherName.NormalizeCSharpAcronyms(); - if (!normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || - !string.Equals($"{normalizedName[..^ResponseSuffix.Length]}Result", resultName, StringComparison.OrdinalIgnoreCase) || - HasLastContractName(otherNamespace, otherName)) - { - return false; - } - // Preserve existing names first, then give historical Result ownership precedence, // and finally choose a stable owner for new competing Response models. - if (HasLastContractName(otherNamespace, resultName)) - { - return true; - } - - if (HasLastContractName(BuildNamespace(), resultName)) - { - return false; - } - - return CompareModelIdentity(otherModel, _inputModel) < 0; + return inputType is InputModelType otherModel && + !inputType.IsExactName && + normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal) && + string.Equals($"{normalizedName[..^ResponseSuffix.Length]}Result", resultName, StringComparison.OrdinalIgnoreCase) && + !HasLastContractName(otherNamespace, otherName) && + (HasLastContractName(otherNamespace, resultName) || + (!HasLastContractName(BuildNamespace(), resultName) && + CompareModelIdentity(otherModel, _inputModel) < 0)); } private static int CompareModelIdentity(InputModelType left, InputModelType right) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index e0099b4c235..b11052aa318 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -331,7 +331,7 @@ protected virtual ModelFactoryProvider CreateModelFactoryCore(IEnumerableThe originating , when available, used to preserve /// semantics (such as extensible-enum backing) that reflection alone cannot recover from the resolved type. /// A representing the external type, or null if the type cannot be resolved. - internal CSharpType? CreateExternalType(InputExternalTypeMetadata externalProperties, InputType? inputType = null, bool reportDiagnostic = true) + internal CSharpType? CreateExternalType(InputExternalTypeMetadata externalProperties, InputType? inputType = null) { // Resolve the type: first as a framework type from the fully qualified name (free, no I/O, and the // source of truth for BCL types), then, on a miss, dynamically from the NuGet package named in the @@ -363,11 +363,6 @@ protected virtual ModelFactoryProvider CreateModelFactoryCore(IEnumerable Date: Tue, 22 Sep 2026 16:38:54 +0000 Subject: [PATCH 45/68] refactor(csharp): avoid unnecessary name normalization Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) 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 bb9448c6d84..d4c70a40667 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 @@ -417,17 +417,24 @@ private bool HasConflictingName( return true; } + if (inputType is not InputModelType otherModel || inputType.IsExactName) + { + return false; + } + var normalizedName = otherName.NormalizeCSharpAcronyms(); + if (!normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || + !string.Equals($"{normalizedName[..^ResponseSuffix.Length]}Result", resultName, StringComparison.OrdinalIgnoreCase) || + HasLastContractName(otherNamespace, otherName)) + { + return false; + } + // Preserve existing names first, then give historical Result ownership precedence, // and finally choose a stable owner for new competing Response models. - return inputType is InputModelType otherModel && - !inputType.IsExactName && - normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal) && - string.Equals($"{normalizedName[..^ResponseSuffix.Length]}Result", resultName, StringComparison.OrdinalIgnoreCase) && - !HasLastContractName(otherNamespace, otherName) && - (HasLastContractName(otherNamespace, resultName) || - (!HasLastContractName(BuildNamespace(), resultName) && - CompareModelIdentity(otherModel, _inputModel) < 0)); + return HasLastContractName(otherNamespace, resultName) || + (!HasLastContractName(BuildNamespace(), resultName) && + CompareModelIdentity(otherModel, _inputModel) < 0); } private static int CompareModelIdentity(InputModelType left, InputModelType right) From 97911d4d9b401beb51f726c22e839c7768dc6011 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:39:57 +0000 Subject: [PATCH 46/68] test(csharp): clarify external collision coverage Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 14 +++++++++++--- .../Providers/ModelProviders/ModelProviderTests.cs | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) 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 d4c70a40667..7fbc72a1468 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 @@ -432,9 +432,17 @@ private bool HasConflictingName( // Preserve existing names first, then give historical Result ownership precedence, // and finally choose a stable owner for new competing Response models. - return HasLastContractName(otherNamespace, resultName) || - (!HasLastContractName(BuildNamespace(), resultName) && - CompareModelIdentity(otherModel, _inputModel) < 0); + if (HasLastContractName(otherNamespace, resultName)) + { + return true; + } + + if (HasLastContractName(BuildNamespace(), resultName)) + { + return false; + } + + return CompareModelIdentity(otherModel, _inputModel) < 0; } private static int CompareModelIdentity(InputModelType left, InputModelType right) 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 66b03230a92..206a16bd38e 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 @@ -438,7 +438,7 @@ public void TestBuildName_ResponseSuffixIgnoresResolvedExternalModelCollision() } [Test] - public void TestBuildName_ResponseSuffixIgnoresUnresolvedExternalModelCollision() + public void TestBuildName_ResponseSuffixIgnoresExternalModelCollision_UnresolvableType() { var response = InputFactory.Model("WidgetResponse"); var externalModel = InputFactory.Model( From ea7633422ba7db18f42d6054a081538502b23d3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:40:50 +0000 Subject: [PATCH 47/68] test(csharp): name external collision scenario Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../test/Providers/ModelProviders/ModelProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 206a16bd38e..b01f0420bbe 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 @@ -438,7 +438,7 @@ public void TestBuildName_ResponseSuffixIgnoresResolvedExternalModelCollision() } [Test] - public void TestBuildName_ResponseSuffixIgnoresExternalModelCollision_UnresolvableType() + public void TestBuildName_ResponseSuffixIgnoresUnresolvableExternalModelCollision() { var response = InputFactory.Model("WidgetResponse"); var externalModel = InputFactory.Model( From eaa3cd0e1ae49f427835a75db9a3bf42acd2d2a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:41:47 +0000 Subject: [PATCH 48/68] test(csharp): clarify external model collision cases Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../test/Providers/ModelProviders/ModelProviderTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 b01f0420bbe..34f478ee443 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 @@ -411,7 +411,7 @@ public void TestBuildName_ResponseSuffixIgnoresResolvedExternalEnumCollision() } [Test] - public void TestBuildName_ResponseSuffixIgnoresExternalModelPhysicalNameCollision() + public void TestBuildName_ResponseSuffixIgnoresExternalModelResolvingToClrType() { var response = InputFactory.Model("WidgetResponse"); var externalModel = InputFactory.Model( @@ -426,7 +426,7 @@ public void TestBuildName_ResponseSuffixIgnoresExternalModelPhysicalNameCollisio } [Test] - public void TestBuildName_ResponseSuffixIgnoresResolvedExternalModelCollision() + public void TestBuildName_ResponseSuffixIgnoresExternalModelWithPhysicalName() { var response = InputFactory.Model("WidgetResponse", @namespace: typeof(WidgetResult).Namespace!); var externalModel = InputFactory.Model( @@ -434,6 +434,7 @@ public void TestBuildName_ResponseSuffixIgnoresResolvedExternalModelCollision() external: new InputExternalTypeMetadata(typeof(WidgetResult).AssemblyQualifiedName!, null, null)); MockHelpers.LoadMockGenerator(inputModelTypes: [response, externalModel]); + // External metadata excludes the model from generated-name collision checks. Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); } From e1f18d337a4df409109fd3c6544dff89ab142b31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:42:57 +0000 Subject: [PATCH 49/68] test(csharp): distinguish external model collision cases Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../test/Providers/ModelProviders/ModelProviderTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 34f478ee443..275a602cc9c 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 @@ -411,7 +411,7 @@ public void TestBuildName_ResponseSuffixIgnoresResolvedExternalEnumCollision() } [Test] - public void TestBuildName_ResponseSuffixIgnoresExternalModelResolvingToClrType() + public void TestBuildName_ResponseSuffixIgnoresExternalModelWithMatchingClrType() { var response = InputFactory.Model("WidgetResponse"); var externalModel = InputFactory.Model( @@ -426,7 +426,7 @@ public void TestBuildName_ResponseSuffixIgnoresExternalModelResolvingToClrType() } [Test] - public void TestBuildName_ResponseSuffixIgnoresExternalModelWithPhysicalName() + public void TestBuildName_ResponseSuffixIgnoresExternalModelWithResultInputName() { var response = InputFactory.Model("WidgetResponse", @namespace: typeof(WidgetResult).Namespace!); var externalModel = InputFactory.Model( From e9fa9f76fec8799bfea6a2c8999faa63aabf5436 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:43:49 +0000 Subject: [PATCH 50/68] test(csharp): clarify external metadata behavior Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../test/Providers/ModelProviders/ModelProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 275a602cc9c..9071f73314c 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 @@ -439,7 +439,7 @@ public void TestBuildName_ResponseSuffixIgnoresExternalModelWithResultInputName( } [Test] - public void TestBuildName_ResponseSuffixIgnoresUnresolvableExternalModelCollision() + public void TestBuildName_ResponseSuffixIgnoresExternalModelRegardlessOfResolution() { var response = InputFactory.Model("WidgetResponse"); var externalModel = InputFactory.Model( From 798cdeebcbb4ed75397219da80245b6cb4510d89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:44:41 +0000 Subject: [PATCH 51/68] test(csharp): name unresolvable external coverage Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../test/Providers/ModelProviders/ModelProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9071f73314c..44dd803f950 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 @@ -439,7 +439,7 @@ public void TestBuildName_ResponseSuffixIgnoresExternalModelWithResultInputName( } [Test] - public void TestBuildName_ResponseSuffixIgnoresExternalModelRegardlessOfResolution() + public void TestBuildName_ResponseSuffixIgnoresUnresolvableExternalModel() { var response = InputFactory.Model("WidgetResponse"); var externalModel = InputFactory.Model( From df7b9f3eb828b5134bd05736df834dcee0b6bf5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:45:38 +0000 Subject: [PATCH 52/68] test(csharp): clarify unresolvable CLR scenario Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../test/Providers/ModelProviders/ModelProviderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 44dd803f950..47ed56d161c 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 @@ -439,7 +439,7 @@ public void TestBuildName_ResponseSuffixIgnoresExternalModelWithResultInputName( } [Test] - public void TestBuildName_ResponseSuffixIgnoresUnresolvableExternalModel() + public void TestBuildName_ResponseSuffixIgnoresExternalModelWithUnresolvableClrType() { var response = InputFactory.Model("WidgetResponse"); var externalModel = InputFactory.Model( From 78ff950544864d28ffb7c5c8d91beccaebcbc601 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 18:05:02 +0000 Subject: [PATCH 53/68] refactor(csharp): simplify emitted type caches Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) 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 7fbc72a1468..a4a3d651e0c 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 @@ -78,7 +78,8 @@ protected override FormattableString BuildDescription() private readonly bool _isDiscriminatedBaseType; // The input library is fixed before providers are named. Cache the emitted collision inventory // once per library instead of rebuilding it for every ModelProvider that checks a Response->Result name. - private static readonly ConditionalWeakTable _emittedTypesCache = new(); + private static readonly ConditionalWeakTable> _emittedModelsCache = new(); + private static readonly ConditionalWeakTable> _emittedEnumsCache = new(); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -353,24 +354,17 @@ private bool HasConflictingName( string resultName, TypeProvider? excludedCustomization = null) { - var emittedTypes = GetEmittedTypes(); - return emittedTypes.Models.Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || - emittedTypes.Enums.Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || + return GetEmittedModels().Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || + GetEmittedEnums().Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); } - private static EmittedTypes GetEmittedTypes() - => _emittedTypesCache.GetValue( + private static IReadOnlyList GetEmittedModels() + => _emittedModelsCache.GetValue( CodeModelGenerator.Instance, - static generator => new( - BuildEmittedModels(generator.InputLibrary).ToList(), - generator.InputLibrary.InputNamespace.Enums - // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external - // enums always map to existing types instead of generated files. - .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - .ToList())); + static generator => GetEmittedModels(generator.InputLibrary).ToList()); - private static IEnumerable BuildEmittedModels(InputLibrary inputLibrary) + private static IEnumerable GetEmittedModels(InputLibrary inputLibrary) { foreach (var model in inputLibrary.InputNamespace.Models) { @@ -389,7 +383,14 @@ private static IEnumerable BuildEmittedModels(InputLibrary input } } - private sealed record EmittedTypes(IReadOnlyList Models, IReadOnlyList Enums); + private static IReadOnlyList GetEmittedEnums() + => _emittedEnumsCache.GetValue( + CodeModelGenerator.Instance, + static generator => generator.InputLibrary.InputNamespace.Enums + // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external + // enums always map to existing types instead of generated files. + .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) + .ToList()); private bool HasConflictingName( InputType inputType, @@ -553,10 +554,9 @@ private bool HasCustomizedSiblingInputName(string typeName) } var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - var emittedTypes = GetEmittedTypes(); - foreach (var (inputType, inputTypeNamespace) in emittedTypes.Models + foreach (var (inputType, inputTypeNamespace) in GetEmittedModels() .Select(model => ((InputType)model, model.Namespace)) - .Concat(emittedTypes.Enums.Select(@enum => ((InputType)@enum, @enum.Namespace)))) + .Concat(GetEmittedEnums().Select(@enum => ((InputType)@enum, @enum.Namespace)))) { if (inputType == _inputModel) { From 6b997fdece76c3c3b35e7c90cadc5f2b50812863 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 18:15:33 +0000 Subject: [PATCH 54/68] refactor(csharp): restore external type encapsulation Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs index b11052aa318..17dcbd3808f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs @@ -331,7 +331,7 @@ protected virtual ModelFactoryProvider CreateModelFactoryCore(IEnumerableThe originating , when available, used to preserve /// semantics (such as extensible-enum backing) that reflection alone cannot recover from the resolved type. /// A representing the external type, or null if the type cannot be resolved. - internal CSharpType? CreateExternalType(InputExternalTypeMetadata externalProperties, InputType? inputType = null) + private CSharpType? CreateExternalType(InputExternalTypeMetadata externalProperties, InputType? inputType = null) { // Resolve the type: first as a framework type from the fully qualified name (free, no I/O, and the // source of truth for BCL types), then, on a miss, dynamically from the NuGet package named in the From 7fc54e2de9c8b8a1d3c360a7f95d6e477519cc23 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 18:23:03 +0000 Subject: [PATCH 55/68] refactor(csharp): inline emitted type caches Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) 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 a4a3d651e0c..32f04e3af4c 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 @@ -354,16 +354,21 @@ private bool HasConflictingName( string resultName, TypeProvider? excludedCustomization = null) { - return GetEmittedModels().Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || - GetEmittedEnums().Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || + return _emittedModelsCache.GetValue( + CodeModelGenerator.Instance, + static generator => GetEmittedModels(generator.InputLibrary).ToList()) + .Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || + _emittedEnumsCache.GetValue( + CodeModelGenerator.Instance, + static generator => generator.InputLibrary.InputNamespace.Enums + // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external + // enums always map to existing types instead of generated files. + .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) + .ToList()) + .Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); } - private static IReadOnlyList GetEmittedModels() - => _emittedModelsCache.GetValue( - CodeModelGenerator.Instance, - static generator => GetEmittedModels(generator.InputLibrary).ToList()); - private static IEnumerable GetEmittedModels(InputLibrary inputLibrary) { foreach (var model in inputLibrary.InputNamespace.Models) @@ -383,15 +388,6 @@ private static IEnumerable GetEmittedModels(InputLibrary inputLi } } - private static IReadOnlyList GetEmittedEnums() - => _emittedEnumsCache.GetValue( - CodeModelGenerator.Instance, - static generator => generator.InputLibrary.InputNamespace.Enums - // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external - // enums always map to existing types instead of generated files. - .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - .ToList()); - private bool HasConflictingName( InputType inputType, string inputTypeNamespace, @@ -554,9 +550,18 @@ private bool HasCustomizedSiblingInputName(string typeName) } var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - foreach (var (inputType, inputTypeNamespace) in GetEmittedModels() + foreach (var (inputType, inputTypeNamespace) in _emittedModelsCache.GetValue( + CodeModelGenerator.Instance, + static generator => GetEmittedModels(generator.InputLibrary).ToList()) .Select(model => ((InputType)model, model.Namespace)) - .Concat(GetEmittedEnums().Select(@enum => ((InputType)@enum, @enum.Namespace)))) + .Concat(_emittedEnumsCache.GetValue( + CodeModelGenerator.Instance, + static generator => generator.InputLibrary.InputNamespace.Enums + // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external + // enums always map to existing types instead of generated files. + .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) + .ToList()) + .Select(@enum => ((InputType)@enum, @enum.Namespace)))) { if (inputType == _inputModel) { From 261fd7764b04b52002e16adf9ee178992f42f25c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 20:18:10 +0000 Subject: [PATCH 56/68] refactor(csharp): consolidate emitted type inventory Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 127 +++++++++--------- 1 file changed, 66 insertions(+), 61 deletions(-) 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 32f04e3af4c..484764579d8 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 @@ -76,10 +76,15 @@ protected override FormattableString BuildDescription() internal PropertyProvider? DiscriminatorProperty { get; private set; } private readonly bool _isDiscriminatedBaseType; - // The input library is fixed before providers are named. Cache the emitted collision inventory - // once per library instead of rebuilding it for every ModelProvider that checks a Response->Result name. - private static readonly ConditionalWeakTable> _emittedModelsCache = new(); - private static readonly ConditionalWeakTable> _emittedEnumsCache = new(); + // The input library is fixed before providers are named. Cache the emitted collision inventory, + // including each type's C# name and namespace, once per library instead of recomputing it for + // every ModelProvider that checks a Response->Result name. + private static readonly ConditionalWeakTable> _emittedTypesCache = new(); + + private static IReadOnlyList<(InputType Type, string Namespace, string Name)> EmittedTypes + => _emittedTypesCache.GetValue( + CodeModelGenerator.Instance, + static generator => GetEmittedTypes(generator.InputLibrary.InputNamespace).ToList()); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -339,12 +344,19 @@ private protected override string NormalizeTypeName(string name) var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; var inputLibrary = CodeModelGenerator.Instance.InputLibrary; var customType = sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, resultName, DeclaringTypeName); - return (customType is not null && - (string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase) || - HasConflictingName(inputLibrary, customType.Type.Namespace, customType.Name, customType))) || - HasConflictingName(inputLibrary, typeNamespace, resultName) - ? normalizedName - : resultName; + if (customType is not null) + { + // A customization occupying the Result name blocks the rename unless it physically renames + // itself elsewhere and no other type claims the name it moved to. + var isCustomTypeRenamed = !string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); + if (!isCustomTypeRenamed || + HasConflictingName(inputLibrary, customType.Type.Namespace, customType.Name, customType)) + { + return normalizedName; + } + } + + return HasConflictingName(inputLibrary, typeNamespace, resultName) ? normalizedName : resultName; } // Model and enum files share a flat output directory, even across namespaces. @@ -354,43 +366,57 @@ private bool HasConflictingName( string resultName, TypeProvider? excludedCustomization = null) { - return _emittedModelsCache.GetValue( - CodeModelGenerator.Instance, - static generator => GetEmittedModels(generator.InputLibrary).ToList()) - .Any(model => HasConflictingName(model, model.Namespace, resultName, excludedCustomization)) || - _emittedEnumsCache.GetValue( - CodeModelGenerator.Instance, - static generator => generator.InputLibrary.InputNamespace.Enums - // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external - // enums always map to existing types instead of generated files. - .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - .ToList()) - .Any(@enum => HasConflictingName(@enum, @enum.Namespace, resultName, excludedCustomization)) || - inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); + foreach (var (inputType, inputTypeNamespace, inputTypeName) in EmittedTypes) + { + if (HasConflictingName(inputType, inputTypeNamespace, inputTypeName, resultName, excludedCustomization)) + { + return true; + } + } + + return inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); } - private static IEnumerable GetEmittedModels(InputLibrary inputLibrary) + private static IEnumerable<(InputType Type, string Namespace, string Name)> GetEmittedTypes(InputNamespace inputNamespace) { - foreach (var model in inputLibrary.InputNamespace.Models) + foreach (var model in inputNamespace.Models) { + // Mirrors OutputLibrary.BuildModels: external models always map to existing types + // instead of generated files. if (model.External is not null) { continue; } - yield return model; + yield return GetEmittedType(model, model.Namespace); - var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(model => model.IsUnknownDiscriminatorModel); + var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(subtype => subtype.IsUnknownDiscriminatorModel); if (unknownVariant is { External: null }) { - yield return unknownVariant; + yield return GetEmittedType(unknownVariant, unknownVariant.Namespace); } } + + foreach (var @enum in inputNamespace.Enums) + { + // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external + // enums always map to existing types instead of generated files. + if (@enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) + { + yield return GetEmittedType(@enum, @enum.Namespace); + } + } + + static (InputType Type, string Namespace, string Name) GetEmittedType(InputType inputType, string inputTypeNamespace) + => (inputType, + GetTypeNamespace(inputTypeNamespace), + inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName()); } private bool HasConflictingName( InputType inputType, - string inputTypeNamespace, + string otherNamespace, + string otherName, string resultName, TypeProvider? excludedCustomization) { @@ -399,8 +425,6 @@ private bool HasConflictingName( return false; } - var otherName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); - var otherNamespace = GetTypeNamespace(inputTypeNamespace); var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); if (customType is not null && !IsSameCustomization(customType, excludedCustomization)) { @@ -549,29 +573,16 @@ private bool HasCustomizedSiblingInputName(string typeName) return false; } - var inputNamespace = CodeModelGenerator.Instance.InputLibrary.InputNamespace; - foreach (var (inputType, inputTypeNamespace) in _emittedModelsCache.GetValue( - CodeModelGenerator.Instance, - static generator => GetEmittedModels(generator.InputLibrary).ToList()) - .Select(model => ((InputType)model, model.Namespace)) - .Concat(_emittedEnumsCache.GetValue( - CodeModelGenerator.Instance, - static generator => generator.InputLibrary.InputNamespace.Enums - // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external - // enums always map to existing types instead of generated files. - .Where(@enum => @enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - .ToList()) - .Select(@enum => ((InputType)@enum, @enum.Namespace)))) - { - if (inputType == _inputModel) + foreach (var (inputType, inputTypeNamespace, inputTypeName) in EmittedTypes) + { + if (inputType == _inputModel || + !string.Equals(inputTypeName, typeName, StringComparison.OrdinalIgnoreCase)) { continue; } - var inputTypeName = inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName(); if (HasMismatchedCustomization( - inputTypeName, - GetTypeNamespace(inputTypeNamespace), + inputTypeNamespace, typeName, GetCustomizationLookupNames(inputType, inputTypeName))) { @@ -579,28 +590,22 @@ private bool HasCustomizedSiblingInputName(string typeName) } } - return inputNamespace.Clients.Any(client => + return CodeModelGenerator.Instance.InputLibrary.InputNamespace.Clients.Any(client => { var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - return HasMismatchedCustomization( - clientName, - GetTypeNamespace(client.Namespace), - typeName, - [clientName]); + return string.Equals(clientName, typeName, StringComparison.OrdinalIgnoreCase) && + HasMismatchedCustomization( + GetTypeNamespace(client.Namespace), + typeName, + [clientName]); }); } private bool HasMismatchedCustomization( - string siblingName, string siblingNamespace, string typeName, IEnumerable customizationLookupNames) { - if (!string.Equals(siblingName, typeName, StringComparison.OrdinalIgnoreCase)) - { - return false; - } - var customType = FindCustomizationType(siblingNamespace, customizationLookupNames); return customType is not null && !string.Equals(customType.Name, typeName, StringComparison.OrdinalIgnoreCase); From 3ff3b37052662c200bf041c5c4eb6299511cecec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 20:40:23 +0000 Subject: [PATCH 57/68] fix(csharp): break Result ownership ties deterministically Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 20 ++++++++---- .../ModelProviders/ModelProviderTests.cs | 32 +++++++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) 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 484764579d8..8670e4802ed 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 @@ -452,7 +452,8 @@ private bool HasConflictingName( } // Preserve existing names first, then give historical Result ownership precedence, - // and finally choose a stable owner for new competing Response models. + // and finally choose a stable owner for new competing Response models. Indistinguishable + // identities yield to the other model so neither claims the shared Result name. if (HasLastContractName(otherNamespace, resultName)) { return true; @@ -463,16 +464,23 @@ private bool HasConflictingName( return false; } - return CompareModelIdentity(otherModel, _inputModel) < 0; + return CompareModelIdentity(otherModel, _inputModel) <= 0; } private static int CompareModelIdentity(InputModelType left, InputModelType right) { - // The lower stable identity claims the Result name. + // The lower stable identity claims the Result name. The spec name is the final tie-breaker + // because the cross-language definition id is optional and defaults to an empty string. var namespaceComparison = string.Compare(left.Namespace, right.Namespace, StringComparison.Ordinal); - return namespaceComparison != 0 - ? namespaceComparison - : string.Compare(left.CrossLanguageDefinitionId, right.CrossLanguageDefinitionId, StringComparison.Ordinal); + if (namespaceComparison != 0) + { + return namespaceComparison; + } + + var definitionIdComparison = string.Compare(left.CrossLanguageDefinitionId, right.CrossLanguageDefinitionId, StringComparison.Ordinal); + return definitionIdComparison != 0 + ? definitionIdComparison + : string.Compare(left.Name, right.Name, StringComparison.Ordinal); } private bool HasConflictingName( 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 47ed56d161c..a53a3cf9e88 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 @@ -320,6 +320,38 @@ public void TestBuildName_ResponseSuffixSelectsSingleResultForDuplicateEffective Assert.AreEqual("FooResponse", secondProvider.Name); } + [TestCase(false)] + [TestCase(true)] + public void TestBuildName_ResponseSuffixSelectsSingleResultWhenDefinitionIdsAreEmpty(bool reverseOrder) + { + var upperAcronym = InputFactory.Model("IPResponse"); + var lowerAcronym = InputFactory.Model("IpResponse"); + upperAcronym.Update(crossLanguageDefinitionId: string.Empty); + lowerAcronym.Update(crossLanguageDefinitionId: string.Empty); + InputModelType[] models = reverseOrder ? [lowerAcronym, upperAcronym] : [upperAcronym, lowerAcronym]; + MockHelpers.LoadMockGenerator(inputModelTypes: models); + + var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); + + CollectionAssert.AreEquivalent(new[] { "IPResult", "IPResponse" }, providers.Select(p => p.Name)); + } + + [TestCase(false)] + [TestCase(true)] + public void TestBuildName_ResponseSuffixKeepsResponseForIndistinguishableModels(bool reverseOrder) + { + var first = InputFactory.Model("WidgetResponse"); + var second = InputFactory.Model("WidgetResponse"); + first.Update(crossLanguageDefinitionId: string.Empty); + second.Update(crossLanguageDefinitionId: string.Empty); + InputModelType[] models = reverseOrder ? [second, first] : [first, second]; + MockHelpers.LoadMockGenerator(inputModelTypes: models); + + var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); + + CollectionAssert.AreEquivalent(new[] { "WidgetResponse", "WidgetResponse" }, providers.Select(p => p.Name)); + } + [TestCase(false)] [TestCase(true)] public async Task TestBuildName_ResponseSuffixPreservesResultOwnerFromLastContract(bool reverseOrder) From fa1f0441796199e1f9c080eff1da6507ce6a37e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 21:01:50 +0000 Subject: [PATCH 58/68] refactor(csharp): move emitted type filtering to InputLibrary Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/InputLibrary.cs | 38 ++++++++++++++ .../src/Providers/ModelProvider.cs | 50 +++++-------------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs index dee7334178c..17c77580f79 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs @@ -2,7 +2,9 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.IO; +using System.Linq; namespace Microsoft.TypeSpec.Generator.Input { @@ -48,6 +50,42 @@ internal InputNamespace Load() private bool? _hasXmlModelSerialization; public bool HasXmlModelSerialization => _hasXmlModelSerialization ??= GetHasXmlModelSerialization(); + private IReadOnlyList? _emittedModels; + /// + /// The models that are emitted as generated types. External models are excluded because they + /// always map to types owned by another library instead of a generated file. + /// + public IReadOnlyList EmittedModels => _emittedModels ??= [.. GetEmittedModels()]; + + private IReadOnlyList? _emittedEnums; + /// + /// The enums that are emitted as generated types. API version enums are never emitted, and external + /// enums always map to types owned by another library instead of a generated file. + /// + public IReadOnlyList EmittedEnums => _emittedEnums ??= [.. InputNamespace.Enums + .Where(e => e.External is null && !e.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum))]; + + private IEnumerable GetEmittedModels() + { + foreach (var model in InputNamespace.Models) + { + if (model.External is not null) + { + continue; + } + + yield return model; + + // Unknown discriminator variants are synthesized alongside their base model rather than + // being listed in the input namespace. + var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(s => s.IsUnknownDiscriminatorModel); + if (unknownVariant is { External: null }) + { + yield return unknownVariant; + } + } + } + private bool GetHasMultipartFormDataOperation() { foreach (var client in InputNamespace.Clients) 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 8670e4802ed..f248e53fc88 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 @@ -76,15 +76,15 @@ protected override FormattableString BuildDescription() internal PropertyProvider? DiscriminatorProperty { get; private set; } private readonly bool _isDiscriminatedBaseType; - // The input library is fixed before providers are named. Cache the emitted collision inventory, - // including each type's C# name and namespace, once per library instead of recomputing it for - // every ModelProvider that checks a Response->Result name. + // InputLibrary decides which types are emitted; this cache only adds the C# name and namespace + // projection, which depends on the generator's TypeFactory. Both are fixed before providers are + // named, so compute it once per generator instead of for every Response->Result name check. private static readonly ConditionalWeakTable> _emittedTypesCache = new(); private static IReadOnlyList<(InputType Type, string Namespace, string Name)> EmittedTypes => _emittedTypesCache.GetValue( CodeModelGenerator.Instance, - static generator => GetEmittedTypes(generator.InputLibrary.InputNamespace).ToList()); + static generator => GetEmittedTypes(generator.InputLibrary).ToList()); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -377,41 +377,15 @@ private bool HasConflictingName( return inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); } - private static IEnumerable<(InputType Type, string Namespace, string Name)> GetEmittedTypes(InputNamespace inputNamespace) - { - foreach (var model in inputNamespace.Models) - { - // Mirrors OutputLibrary.BuildModels: external models always map to existing types - // instead of generated files. - if (model.External is not null) - { - continue; - } - - yield return GetEmittedType(model, model.Namespace); - - var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(subtype => subtype.IsUnknownDiscriminatorModel); - if (unknownVariant is { External: null }) - { - yield return GetEmittedType(unknownVariant, unknownVariant.Namespace); - } - } - - foreach (var @enum in inputNamespace.Enums) - { - // Mirrors OutputLibrary.BuildEnums: API-version enums are never emitted, and external - // enums always map to existing types instead of generated files. - if (@enum.External is null && !@enum.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum)) - { - yield return GetEmittedType(@enum, @enum.Namespace); - } - } + private static IEnumerable<(InputType Type, string Namespace, string Name)> GetEmittedTypes(InputLibrary inputLibrary) + => inputLibrary.EmittedModels + .Select(model => GetEmittedType(model, model.Namespace)) + .Concat(inputLibrary.EmittedEnums.Select(@enum => GetEmittedType(@enum, @enum.Namespace))); - static (InputType Type, string Namespace, string Name) GetEmittedType(InputType inputType, string inputTypeNamespace) - => (inputType, - GetTypeNamespace(inputTypeNamespace), - inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName()); - } + private static (InputType Type, string Namespace, string Name) GetEmittedType(InputType inputType, string inputTypeNamespace) + => (inputType, + GetTypeNamespace(inputTypeNamespace), + inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName()); private bool HasConflictingName( InputType inputType, From baed7a87736e908eaef947d2caa717910ad67991 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 22:07:38 +0000 Subject: [PATCH 59/68] refactor(csharp): internalize non-external type inventories Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/InputLibrary.cs | 14 ++++++------- .../src/Properties/AssemblyInfo.cs | 1 + .../src/Providers/ModelProvider.cs | 21 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs index 17c77580f79..23b53b4d564 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs @@ -50,22 +50,22 @@ internal InputNamespace Load() private bool? _hasXmlModelSerialization; public bool HasXmlModelSerialization => _hasXmlModelSerialization ??= GetHasXmlModelSerialization(); - private IReadOnlyList? _emittedModels; + private IReadOnlyList? _nonExternalModels; /// - /// The models that are emitted as generated types. External models are excluded because they + /// The non-external models. External models are excluded because they /// always map to types owned by another library instead of a generated file. /// - public IReadOnlyList EmittedModels => _emittedModels ??= [.. GetEmittedModels()]; + internal IReadOnlyList NonExternalModels => _nonExternalModels ??= [.. GetNonExternalModels()]; - private IReadOnlyList? _emittedEnums; + private IReadOnlyList? _nonExternalEnums; /// - /// The enums that are emitted as generated types. API version enums are never emitted, and external + /// The non-external API-version-excluding enums. API version enums are never emitted, and external /// enums always map to types owned by another library instead of a generated file. /// - public IReadOnlyList EmittedEnums => _emittedEnums ??= [.. InputNamespace.Enums + internal IReadOnlyList NonExternalEnums => _nonExternalEnums ??= [.. InputNamespace.Enums .Where(e => e.External is null && !e.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum))]; - private IEnumerable GetEmittedModels() + private IEnumerable GetNonExternalModels() { foreach (var model in InputNamespace.Models) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs index f3131262a05..e8f21552f19 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs @@ -6,3 +6,4 @@ [assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator.Input.Tests.Perf, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] [assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator.Input.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] [assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator.Tests.Common, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] +[assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] 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 f248e53fc88..7e523e2db51 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 @@ -84,7 +84,16 @@ protected override FormattableString BuildDescription() private static IReadOnlyList<(InputType Type, string Namespace, string Name)> EmittedTypes => _emittedTypesCache.GetValue( CodeModelGenerator.Instance, - static generator => GetEmittedTypes(generator.InputLibrary).ToList()); + static _ => CodeModelGenerator.Instance.InputLibrary.NonExternalModels + .Select(model => ( + (InputType)model, + ModelProvider.GetTypeNamespace(model.Namespace), + model.IsExactName ? model.Name : model.Name.ToIdentifierName())) + .Concat(CodeModelGenerator.Instance.InputLibrary.NonExternalEnums.Select(@enum => ( + (InputType)@enum, + ModelProvider.GetTypeNamespace(@enum.Namespace), + @enum.IsExactName ? @enum.Name : @enum.Name.ToIdentifierName()))) + .ToList()); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -377,16 +386,6 @@ private bool HasConflictingName( return inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); } - private static IEnumerable<(InputType Type, string Namespace, string Name)> GetEmittedTypes(InputLibrary inputLibrary) - => inputLibrary.EmittedModels - .Select(model => GetEmittedType(model, model.Namespace)) - .Concat(inputLibrary.EmittedEnums.Select(@enum => GetEmittedType(@enum, @enum.Namespace))); - - private static (InputType Type, string Namespace, string Name) GetEmittedType(InputType inputType, string inputTypeNamespace) - => (inputType, - GetTypeNamespace(inputTypeNamespace), - inputType.IsExactName ? inputType.Name : inputType.Name.ToIdentifierName()); - private bool HasConflictingName( InputType inputType, string otherNamespace, From 24c8f9d22d5ed7a0cc86981bcdf931582678ef07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 22:28:05 +0000 Subject: [PATCH 60/68] refactor(csharp): remove duplicate response ownership Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 49 +------------- .../ModelProviders/ModelProviderTests.cs | 66 ------------------- 2 files changed, 1 insertion(+), 114 deletions(-) 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 7e523e2db51..01c06b94571 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 @@ -406,54 +406,7 @@ private bool HasConflictingName( } // Acronym normalization only changes casing, so this also covers the normalized filename. - if (string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - if (inputType is not InputModelType otherModel || inputType.IsExactName) - { - return false; - } - - var normalizedName = otherName.NormalizeCSharpAcronyms(); - if (!normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || - !string.Equals($"{normalizedName[..^ResponseSuffix.Length]}Result", resultName, StringComparison.OrdinalIgnoreCase) || - HasLastContractName(otherNamespace, otherName)) - { - return false; - } - - // Preserve existing names first, then give historical Result ownership precedence, - // and finally choose a stable owner for new competing Response models. Indistinguishable - // identities yield to the other model so neither claims the shared Result name. - if (HasLastContractName(otherNamespace, resultName)) - { - return true; - } - - if (HasLastContractName(BuildNamespace(), resultName)) - { - return false; - } - - return CompareModelIdentity(otherModel, _inputModel) <= 0; - } - - private static int CompareModelIdentity(InputModelType left, InputModelType right) - { - // The lower stable identity claims the Result name. The spec name is the final tie-breaker - // because the cross-language definition id is optional and defaults to an empty string. - var namespaceComparison = string.Compare(left.Namespace, right.Namespace, StringComparison.Ordinal); - if (namespaceComparison != 0) - { - return namespaceComparison; - } - - var definitionIdComparison = string.Compare(left.CrossLanguageDefinitionId, right.CrossLanguageDefinitionId, StringComparison.Ordinal); - return definitionIdComparison != 0 - ? definitionIdComparison - : string.Compare(left.Name, right.Name, StringComparison.Ordinal); + return string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase); } private bool HasConflictingName( 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 a53a3cf9e88..6e444fed920 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 @@ -304,72 +304,6 @@ public void TestBuildName_ResponseSuffixAvoidsModelCollision( CollectionAssert.AreEquivalent(new[] { "WidgetResponse", resultName }, providers.Select(p => p.Name)); } - [TestCase(false)] - [TestCase(true)] - public void TestBuildName_ResponseSuffixSelectsSingleResultForDuplicateEffectiveNames(bool reverseOrder) - { - var first = InputFactory.Model("FooResponse", @namespace: "Alpha"); - var second = InputFactory.Model("FooResponse", @namespace: "Beta"); - InputModelType[] models = reverseOrder ? [second, first] : [first, second]; - MockHelpers.LoadMockGenerator(inputModelTypes: models); - - var firstProvider = CodeModelGenerator.Instance.TypeFactory.CreateModel(first)!; - var secondProvider = CodeModelGenerator.Instance.TypeFactory.CreateModel(second)!; - - Assert.AreEqual("FooResult", firstProvider.Name); - Assert.AreEqual("FooResponse", secondProvider.Name); - } - - [TestCase(false)] - [TestCase(true)] - public void TestBuildName_ResponseSuffixSelectsSingleResultWhenDefinitionIdsAreEmpty(bool reverseOrder) - { - var upperAcronym = InputFactory.Model("IPResponse"); - var lowerAcronym = InputFactory.Model("IpResponse"); - upperAcronym.Update(crossLanguageDefinitionId: string.Empty); - lowerAcronym.Update(crossLanguageDefinitionId: string.Empty); - InputModelType[] models = reverseOrder ? [lowerAcronym, upperAcronym] : [upperAcronym, lowerAcronym]; - MockHelpers.LoadMockGenerator(inputModelTypes: models); - - var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); - - CollectionAssert.AreEquivalent(new[] { "IPResult", "IPResponse" }, providers.Select(p => p.Name)); - } - - [TestCase(false)] - [TestCase(true)] - public void TestBuildName_ResponseSuffixKeepsResponseForIndistinguishableModels(bool reverseOrder) - { - var first = InputFactory.Model("WidgetResponse"); - var second = InputFactory.Model("WidgetResponse"); - first.Update(crossLanguageDefinitionId: string.Empty); - second.Update(crossLanguageDefinitionId: string.Empty); - InputModelType[] models = reverseOrder ? [second, first] : [first, second]; - MockHelpers.LoadMockGenerator(inputModelTypes: models); - - var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); - - CollectionAssert.AreEquivalent(new[] { "WidgetResponse", "WidgetResponse" }, providers.Select(p => p.Name)); - } - - [TestCase(false)] - [TestCase(true)] - public async Task TestBuildName_ResponseSuffixPreservesResultOwnerFromLastContract(bool reverseOrder) - { - var first = InputFactory.Model("FooResponse", @namespace: "Alpha"); - var shipped = InputFactory.Model("FooResponse", @namespace: "Beta"); - InputModelType[] models = reverseOrder ? [shipped, first] : [first, shipped]; - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: models, - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - var firstProvider = CodeModelGenerator.Instance.TypeFactory.CreateModel(first)!; - var shippedProvider = CodeModelGenerator.Instance.TypeFactory.CreateModel(shipped)!; - - Assert.AreEqual("FooResponse", firstProvider.Name); - Assert.AreEqual("FooResult", shippedProvider.Name); - } - [TestCase("Sample.Models", "WidgetResponse")] [TestCase("Other", "WidgetResult")] public void TestBuildName_ResponseSuffixAvoidsClientCollision(string clientNamespace, string expectedName) From f9eb71942ab8d385a17094788519d9afffc45adf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:01:55 +0000 Subject: [PATCH 61/68] refactor(csharp): remove response collision detection Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/InputLibrary.cs | 38 -- .../src/Properties/AssemblyInfo.cs | 1 - .../src/Providers/ModelProvider.cs | 204 +--------- .../ModelProviders/ModelProviderTests.cs | 352 +----------------- .../CustomWidgetResult.cs | 9 - .../WidgetResult.cs | 9 - .../CustomWidget.cs | 9 - .../WidgetResult.cs | 9 - .../WidgetResult.cs | 6 - .../CustomWidgetResult.cs | 9 - .../IPResponse.cs | 6 - .../WidgetResult.cs | 9 - .../FooModel.cs | 9 - .../FooResult.cs | 6 - .../FooResult.cs | 6 - .../CustomFooResult.cs | 9 - 16 files changed, 3 insertions(+), 688 deletions(-) delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAllowsCustomizedModelResultName/CustomWidgetResult.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsCustomizationCollision/WidgetResult.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsCustomizedPhysicalNameCollision/CustomWidget.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsResultCustomizationAliasCollision/WidgetResult.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixAvoidsStandaloneCustomizationCollision/WidgetResult.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresNonEmittedEnumCustomizationSibling/CustomWidgetResult.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel(LastContract)/IPResponse.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel/WidgetResult.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesCustomResponseRenameWithoutLastContract/FooModel.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesResultOwnerFromLastContract/FooResult.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName(LastContract)/FooResult.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName/CustomFooResult.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs index 23b53b4d564..dee7334178c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/InputLibrary.cs @@ -2,9 +2,7 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using System.IO; -using System.Linq; namespace Microsoft.TypeSpec.Generator.Input { @@ -50,42 +48,6 @@ internal InputNamespace Load() private bool? _hasXmlModelSerialization; public bool HasXmlModelSerialization => _hasXmlModelSerialization ??= GetHasXmlModelSerialization(); - private IReadOnlyList? _nonExternalModels; - /// - /// The non-external models. External models are excluded because they - /// always map to types owned by another library instead of a generated file. - /// - internal IReadOnlyList NonExternalModels => _nonExternalModels ??= [.. GetNonExternalModels()]; - - private IReadOnlyList? _nonExternalEnums; - /// - /// The non-external API-version-excluding enums. API version enums are never emitted, and external - /// enums always map to types owned by another library instead of a generated file. - /// - internal IReadOnlyList NonExternalEnums => _nonExternalEnums ??= [.. InputNamespace.Enums - .Where(e => e.External is null && !e.Usage.HasFlag(InputModelTypeUsage.ApiVersionEnum))]; - - private IEnumerable GetNonExternalModels() - { - foreach (var model in InputNamespace.Models) - { - if (model.External is not null) - { - continue; - } - - yield return model; - - // Unknown discriminator variants are synthesized alongside their base model rather than - // being listed in the input namespace. - var unknownVariant = model.DiscriminatedSubtypes.Values.FirstOrDefault(s => s.IsUnknownDiscriminatorModel); - if (unknownVariant is { External: null }) - { - yield return unknownVariant; - } - } - } - private bool GetHasMultipartFormDataOperation() { foreach (var client in InputNamespace.Clients) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs index e8f21552f19..f3131262a05 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.Input/src/Properties/AssemblyInfo.cs @@ -6,4 +6,3 @@ [assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator.Input.Tests.Perf, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] [assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator.Input.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] [assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator.Tests.Common, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] -[assembly: InternalsVisibleTo("Microsoft.TypeSpec.Generator, PublicKey=002400000480000094000000060200000024000052534131000400000100010041df4fe80c5af6ff9a410db5a173b0ce24ad68764c623e308b1584a88b1d1d82277f746c1cccba48997e13db3366d5ed676576ffd293293baf42c643f008ba2e8a556e25e529c0407a38506555340749559f5100e6fd78cc935bb6c82d2af303beb0d3c6563400659610759b4ed5cb2e0faf36b17e6842f04cdc544c74e051ba")] 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 01c06b94571..c30241dbc8c 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 @@ -6,7 +6,6 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using Microsoft.TypeSpec.Generator.EmitterRpc; using Microsoft.TypeSpec.Generator.Expressions; using Microsoft.TypeSpec.Generator.Input; @@ -76,24 +75,6 @@ protected override FormattableString BuildDescription() internal PropertyProvider? DiscriminatorProperty { get; private set; } private readonly bool _isDiscriminatedBaseType; - // InputLibrary decides which types are emitted; this cache only adds the C# name and namespace - // projection, which depends on the generator's TypeFactory. Both are fixed before providers are - // named, so compute it once per generator instead of for every Response->Result name check. - private static readonly ConditionalWeakTable> _emittedTypesCache = new(); - - private static IReadOnlyList<(InputType Type, string Namespace, string Name)> EmittedTypes - => _emittedTypesCache.GetValue( - CodeModelGenerator.Instance, - static _ => CodeModelGenerator.Instance.InputLibrary.NonExternalModels - .Select(model => ( - (InputType)model, - ModelProvider.GetTypeNamespace(model.Namespace), - model.IsExactName ? model.Name : model.Name.ToIdentifierName())) - .Concat(CodeModelGenerator.Instance.InputLibrary.NonExternalEnums.Select(@enum => ( - (InputType)@enum, - ModelProvider.GetTypeNamespace(@enum.Namespace), - @enum.IsExactName ? @enum.Name : @enum.Name.ToIdentifierName()))) - .ToList()); private ValueExpression DiscriminatorLiteral => Literal(_inputModel.DiscriminatorValue ?? ""); @@ -350,201 +331,20 @@ private protected override string NormalizeTypeName(string name) return normalizedName; } - var resultName = $"{normalizedName[..^ResponseSuffix.Length]}Result"; - var inputLibrary = CodeModelGenerator.Instance.InputLibrary; - var customType = sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, resultName, DeclaringTypeName); - if (customType is not null) - { - // A customization occupying the Result name blocks the rename unless it physically renames - // itself elsewhere and no other type claims the name it moved to. - var isCustomTypeRenamed = !string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); - if (!isCustomTypeRenamed || - HasConflictingName(inputLibrary, customType.Type.Namespace, customType.Name, customType)) - { - return normalizedName; - } - } - - return HasConflictingName(inputLibrary, typeNamespace, resultName) ? normalizedName : resultName; - } - - // Model and enum files share a flat output directory, even across namespaces. - private bool HasConflictingName( - InputLibrary inputLibrary, - string typeNamespace, - string resultName, - TypeProvider? excludedCustomization = null) - { - foreach (var (inputType, inputTypeNamespace, inputTypeName) in EmittedTypes) - { - if (HasConflictingName(inputType, inputTypeNamespace, inputTypeName, resultName, excludedCustomization)) - { - return true; - } - } - - return inputLibrary.InputNamespace.Clients.Any(client => HasConflictingName(client, typeNamespace, resultName, excludedCustomization)); - } - - private bool HasConflictingName( - InputType inputType, - string otherNamespace, - string otherName, - string resultName, - TypeProvider? excludedCustomization) - { - if (inputType == _inputModel) - { - return false; - } - - var customType = FindCustomizationType(otherNamespace, GetCustomizationLookupNames(inputType, otherName)); - if (customType is not null && !IsSameCustomization(customType, excludedCustomization)) - { - return HasLastContractName(otherNamespace, resultName) || - string.Equals(customType.Name, resultName, StringComparison.OrdinalIgnoreCase); - } - - // Acronym normalization only changes casing, so this also covers the normalized filename. - return string.Equals(otherName, resultName, StringComparison.OrdinalIgnoreCase); - } - - private bool HasConflictingName( - InputClient client, - string typeNamespace, - string resultName, - TypeProvider? excludedCustomization) - { - var clientNamespace = GetTypeNamespace(client.Namespace); - var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - var customType = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation(clientNamespace, clientName); - if (IsSameCustomization(customType, excludedCustomization)) - { - customType = null; - } - - return (customType?.Type.Namespace ?? clientNamespace) == typeNamespace && - (customType?.Name ?? clientName) == resultName; - } - - private static bool IsSameCustomization(TypeProvider? left, TypeProvider? right) - => left is NamedTypeSymbolProvider leftSymbol && - right is NamedTypeSymbolProvider rightSymbol && - leftSymbol.MetadataName == rightSymbol.MetadataName; - - private TypeProvider? FindCustomizationType( - string typeNamespace, - IEnumerable lookupNames) - { - var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; - if (sourceInputModel.Customization is null) - { - return null; - } - - foreach (var lookupName in lookupNames) - { - var customType = sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, lookupName); - if (customType is not null) - { - return customType; - } - } - - return null; - } - - private static IEnumerable GetCustomizationLookupNames(InputType inputType, string name) - { - yield return name; - if (inputType.IsExactName) - { - yield break; - } - - var normalizedName = name.NormalizeCSharpAcronyms(); - if (normalizedName != name) - { - yield return normalizedName; - } - - if (inputType is InputModelType && normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) - { - yield return $"{normalizedName[..^ResponseSuffix.Length]}Result"; - } - } - - private bool HasLastContractName(string typeNamespace, string name) - { - var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; - if (sourceInputModel.FindForTypeInLastContract(typeNamespace, name) is not null) - { - return true; - } - - var normalizedName = name.NormalizeCSharpAcronyms(); - return normalizedName != name && - sourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName) is not null; + return $"{normalizedName[..^ResponseSuffix.Length]}Result"; } private protected override TypeProvider? BuildCustomCodeView(string? generatedTypeName = null, string? generatedTypeNamespace = null) { var typeNamespace = generatedTypeNamespace ?? BuildNamespace(); var typeName = generatedTypeName ?? BuildName(); - var customCodeView = HasCustomizedSiblingInputName(typeName) - ? null - : base.BuildCustomCodeView(typeName, typeNamespace); + var customCodeView = base.BuildCustomCodeView(typeName, typeNamespace); return customCodeView ?? BuildResponseSuffixFallbackView( typeName, typeNamespace, (name, ns) => base.BuildCustomCodeView(name, ns)); } - private bool HasCustomizedSiblingInputName(string typeName) - { - if (CodeModelGenerator.Instance.SourceInputModel.Customization is null) - { - return false; - } - - foreach (var (inputType, inputTypeNamespace, inputTypeName) in EmittedTypes) - { - if (inputType == _inputModel || - !string.Equals(inputTypeName, typeName, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - if (HasMismatchedCustomization( - inputTypeNamespace, - typeName, - GetCustomizationLookupNames(inputType, inputTypeName))) - { - return true; - } - } - - return CodeModelGenerator.Instance.InputLibrary.InputNamespace.Clients.Any(client => - { - var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - return string.Equals(clientName, typeName, StringComparison.OrdinalIgnoreCase) && - HasMismatchedCustomization( - GetTypeNamespace(client.Namespace), - typeName, - [clientName]); - }); - } - - private bool HasMismatchedCustomization( - string siblingNamespace, - string typeName, - IEnumerable customizationLookupNames) - { - var customType = FindCustomizationType(siblingNamespace, customizationLookupNames); - return customType is not null && - !string.Equals(customType.Name, typeName, StringComparison.OrdinalIgnoreCase); - } - private static string GetTypeNamespace(string inputNamespace) => string.IsNullOrEmpty(inputNamespace) // TODO remove null check once https://github.com/Azure/typespec-azure/issues/2209 is fixed. 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 6e444fed920..4ae8e1324f0 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,51 +154,6 @@ public void TestBuildName_ResponseSuffix(string inputName, bool isExactName, str Assert.AreEqual("ServiceResponse", model.Properties[0].Name); } - [TestCase("WidgetResponse", false, "WidgetResult")] - [TestCase("WidgetResponse", true, "WidgetResponse")] - [TestCase("IpResponse", false, "IPResult")] - [TestCase("IpResponse", true, "IpResponse")] - [TestCase("widget_response", false, "WidgetResult")] - [TestCase("widget_response", true, "widget_response")] - public void TestBuildName_ResponseSuffixHonorsExactName(string inputName, bool isExactName, string expectedName) - { - var inputModel = InputFactory.Model(inputName, isExactName: isExactName); - MockHelpers.LoadMockGenerator(inputModelTypes: [inputModel]); - - var model = CodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; - - Assert.AreEqual(expectedName, model.Name); - Assert.AreEqual($"{expectedName}.cs", Path.GetFileName(model.RelativeFilePath)); - model.Reset(); - Assert.AreEqual(expectedName, model.Name); - } - - [TestCase("WidgetResponse", false, "WidgetResult")] - [TestCase("WidgetResponse", true, "WidgetResponse")] - [TestCase("IpResponse", false, "IPResult")] - [TestCase("IpResponse", true, "IPResponse")] - [TestCase("DbResponse", false, "DBResult")] - [TestCase("DbResponse", true, "DbResponse")] - public async Task TestBuildName_ResponseSuffixHonorsLastContract( - string inputName, bool hasLastContract, string expectedName) - { - var inputModel = InputFactory.Model(inputName); - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: [inputModel], - lastContractCompilation: hasLastContract - ? async () => await Helpers.GetCompilationFromDirectoryAsync( - method: nameof(TestBuildName_ResponseSuffixPreservesExistingName)) - : null); - - var model = CodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; - - Assert.AreEqual(expectedName, model.Name); - Assert.AreEqual(hasLastContract, model.LastContractView is not null); - Assert.IsNull(model.CustomCodeView); - model.Reset(); - Assert.AreEqual(expectedName, model.Name); - } - [TestCase("WidgetResponse", "WidgetResponse", false, false)] [TestCase("WidgetResponse", "WidgetResponse", true, false)] [TestCase("WidgetResponse", "WidgetResponse", false, true)] @@ -249,301 +204,6 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.IsNotNull(model.CustomCodeView); } - [TestCase("apiVersion")] - [TestCase("external")] - public async Task TestBuildName_ResponseSuffixIgnoresNonEmittedEnumCustomizationSibling(string enumKind) - { - var response = InputFactory.Model("WidgetResponse"); - var nonEmittedEnum = InputFactory.StringEnum( - "WidgetResult", - [("Value", "value")], - usage: enumKind == "apiVersion" ? InputModelTypeUsage.ApiVersionEnum : InputModelTypeUsage.None, - external: enumKind == "external" ? new InputExternalTypeMetadata("System.Net.HttpStatusCode", null, null) : null); - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: [response], - inputEnumTypes: [nonEmittedEnum], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - var model = CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!; - - Assert.AreEqual("CustomWidgetResult", model.Name); - Assert.AreEqual("CustomWidgetResult", model.CustomCodeView?.Name); - } - - [Test] - public async Task TestBuildName_ResponseSuffixPreservesCustomResponseRenameWithoutLastContract() - { - var inputModel = InputFactory.Model("FooResponse"); - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: [inputModel], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - var model = CodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; - - Assert.AreEqual("FooModel", model.Name); - Assert.IsNotNull(model.CustomCodeView); - Assert.IsNull(model.LastContractView); - } - - [TestCase(false, false, "WidgetResult", false)] - [TestCase(false, true, "WidgetResult", false)] - [TestCase(true, false, "WidgetResult", false)] - [TestCase(true, true, "WidgetResult", false)] - [TestCase(false, false, "widgetResult", true)] - public void TestBuildName_ResponseSuffixAvoidsModelCollision( - bool reverseOrder, bool differentNamespace, string resultName, bool isExactName) - { - var response = InputFactory.Model("WidgetResponse"); - var result = InputFactory.Model( - resultName, @namespace: differentNamespace ? "Other" : "Sample.Models", isExactName: isExactName); - InputModelType[] models = reverseOrder ? [result, response] : [response, result]; - MockHelpers.LoadMockGenerator(inputModelTypes: models); - - var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); - - CollectionAssert.AreEquivalent(new[] { "WidgetResponse", resultName }, providers.Select(p => p.Name)); - } - - [TestCase("Sample.Models", "WidgetResponse")] - [TestCase("Other", "WidgetResult")] - public void TestBuildName_ResponseSuffixAvoidsClientCollision(string clientNamespace, string expectedName) - { - var response = InputFactory.Model("WidgetResponse"); - var client = InputFactory.Client("WidgetResult"); - client.Update(@namespace: clientNamespace); - MockHelpers.LoadMockGenerator(inputModelTypes: [response], inputClients: [client]); - - Assert.AreEqual(expectedName, CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - } - - [TestCase(false)] - [TestCase(true)] - public void TestBuildName_ResponseSuffixAvoidsEnumCollision(bool isExtensible) - { - var response = InputFactory.Model("IpResponse"); - var result = InputFactory.StringEnum("IpResult", [("Value", "value")], isExtensible: isExtensible); - MockHelpers.LoadMockGenerator(inputModelTypes: [response], inputEnumTypes: [result]); - - Assert.AreEqual("IPResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - Assert.AreEqual("IPResult", CodeModelGenerator.Instance.TypeFactory.CreateEnum(result)!.Name); - } - - [Test] - public void TestBuildName_ResponseSuffixIgnoresApiVersionEnumCollision() - { - var response = InputFactory.Model("WidgetResponse"); - var apiVersionEnum = InputFactory.StringEnum( - "WidgetResult", - [("Value", "value")], - usage: InputModelTypeUsage.ApiVersionEnum); - MockHelpers.LoadMockGenerator(inputModelTypes: [response], inputEnumTypes: [apiVersionEnum]); - - // The API-version enum is never emitted, so it cannot reserve the "WidgetResult" name. - var provider = CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!; - Assert.AreEqual("WidgetResult", provider.Name); - provider.Reset(); - Assert.AreEqual("WidgetResult", provider.Name); - } - - [Test] - public void TestBuildName_ResponseSuffixIgnoresExternalEnumCollision() - { - var response = InputFactory.Model("WidgetResponse"); - var externalEnum = InputFactory.StringEnum( - "WidgetResult", - [("Value", "value")], - external: new InputExternalTypeMetadata("System.Net.HttpStatusCode", null, null)); - MockHelpers.LoadMockGenerator(inputModelTypes: [response], inputEnumTypes: [externalEnum]); - - // An external enum always maps to an existing type instead of being generated, so it - // cannot reserve the "WidgetResult" name. - var provider = CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!; - Assert.AreEqual("WidgetResult", provider.Name); - provider.Reset(); - Assert.AreEqual("WidgetResult", provider.Name); - } - - [Test] - public void TestBuildName_ResponseSuffixIgnoresResolvedExternalEnumCollision() - { - var response = InputFactory.Model("GadgetResponse", @namespace: typeof(GadgetResult).Namespace!); - var externalEnum = InputFactory.StringEnum( - "ExternalGadget", - [("Value", "value")], - external: new InputExternalTypeMetadata(typeof(GadgetResult).AssemblyQualifiedName!, null, null)); - MockHelpers.LoadMockGenerator(inputModelTypes: [response], inputEnumTypes: [externalEnum]); - - Assert.AreEqual("GadgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - } - - [Test] - public void TestBuildName_ResponseSuffixIgnoresExternalModelWithMatchingClrType() - { - var response = InputFactory.Model("WidgetResponse"); - var externalModel = InputFactory.Model( - "WidgetResult", - external: new InputExternalTypeMetadata("System.Exception", null, null)); - MockHelpers.LoadMockGenerator(inputModelTypes: [response, externalModel]); - - // The external model resolves to an existing framework type, so it is represented by a - // SystemObjectModelProvider and never emitted as a generated file; it cannot reserve - // the "WidgetResult" name. - Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - } - - [Test] - public void TestBuildName_ResponseSuffixIgnoresExternalModelWithResultInputName() - { - var response = InputFactory.Model("WidgetResponse", @namespace: typeof(WidgetResult).Namespace!); - var externalModel = InputFactory.Model( - "ExternalWidget", - external: new InputExternalTypeMetadata(typeof(WidgetResult).AssemblyQualifiedName!, null, null)); - MockHelpers.LoadMockGenerator(inputModelTypes: [response, externalModel]); - - // External metadata excludes the model from generated-name collision checks. - Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - } - - [Test] - public void TestBuildName_ResponseSuffixIgnoresExternalModelWithUnresolvableClrType() - { - var response = InputFactory.Model("WidgetResponse"); - var externalModel = InputFactory.Model( - "WidgetResult", - external: new InputExternalTypeMetadata("Some.Unresolvable.ExternalType", null, null)); - MockHelpers.LoadMockGenerator(inputModelTypes: [response, externalModel]); - - Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - } - - [TestCase("OtherModel")] - [TestCase("WidgetResult")] - public async Task TestBuildName_ResponseSuffixAvoidsCustomizationCollision(string otherName) - { - var response = InputFactory.Model("WidgetResponse"); - var other = InputFactory.Model(otherName); - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: [response, other], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - Assert.AreEqual("WidgetResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(other)!.Name); - } - - [Test] - public async Task TestBuildName_ResponseSuffixAvoidsStandaloneCustomizationCollision() - { - var response = InputFactory.Model("WidgetResponse"); - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: [response], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - var provider = CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!; - - Assert.AreEqual("WidgetResponse", provider.Name); - Assert.IsNull(provider.CustomCodeView); - } - - [Test] - public async Task TestBuildName_ResponseSuffixAllowsCustomizedModelResultName() - { - var response = InputFactory.Model("WidgetResponse"); - var result = InputFactory.Model("WidgetResult"); - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: [response, result], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - Assert.AreEqual("WidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - Assert.AreEqual("CustomWidgetResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(result)!.Name); - } - - [TestCase("model")] - [TestCase("enum")] - [TestCase("client")] - public async Task TestBuildName_ResponseSuffixAvoidsCustomizedPhysicalNameCollision(string typeKind) - { - var response = InputFactory.Model("WidgetResponse"); - var model = InputFactory.Model("CustomWidget"); - var @enum = InputFactory.StringEnum("CustomWidget", [("Value", "value")]); - var client = InputFactory.Client("CustomWidget", clientNamespace: "Sample.Models"); - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: typeKind == "model" ? [response, model] : [response], - inputEnumTypes: typeKind == "enum" ? [@enum] : [], - inputClients: typeKind == "client" ? [client] : [], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - Assert.AreEqual("WidgetResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - } - - [Test] - public void TestBuildName_ResponseSuffixAvoidsUnknownDiscriminatorCollision() - { - var discriminator = InputFactory.Property("kind", InputPrimitiveType.String, isDiscriminator: true); - var result = InputFactory.Model( - "FooResult", - properties: [discriminator], - discriminatedModels: new Dictionary()); - var response = InputFactory.Model("UnknownFooResponse"); - MockHelpers.LoadMockGenerator(inputModelTypes: [result, response]); - - var providers = CodeModelGenerator.Instance.OutputLibrary.TypeProviders.OfType(); - - Assert.AreEqual("UnknownFooResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - Assert.AreEqual(1, providers.Count(provider => provider.Name == "UnknownFooResult")); - } - - [Test] - public async Task TestBuildName_ResponseSuffixPreservesShippedResultCustomizedName() - { - var response = InputFactory.Model("FooResponse"); - var result = InputFactory.Model("FooResult"); - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: [response, result], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(), - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync( - parameters: "LastContract")); - - Assert.AreEqual("FooResponse", CodeModelGenerator.Instance.TypeFactory.CreateModel(response)!.Name); - Assert.AreEqual("CustomFooResult", CodeModelGenerator.Instance.TypeFactory.CreateModel(result)!.Name); - } - - [TestCase(false)] - [TestCase(true)] - public async Task TestBuildName_ResponseSuffixAvoidsResultCustomizationAliasCollision(bool reverseOrder) - { - var widget = InputFactory.Model("WidgetResponse"); - var gadget = InputFactory.Model("GadgetResponse"); - InputModelType[] models = reverseOrder ? [gadget, widget] : [widget, gadget]; - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: models, - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); - - CollectionAssert.AreEquivalent(new[] { "WidgetResponse", "WidgetResult" }, providers.Select(p => p.Name)); - } - - [TestCase("IpResponse", false)] - [TestCase("IpResponse", true)] - [TestCase("IPResponse", false)] - [TestCase("IPResponse", true)] - public async Task TestBuildName_ResponseSuffixIgnoresResultAliasForShippedModel( - string inputName, bool reverseOrder) - { - var widget = InputFactory.Model("WidgetResponse"); - var shipped = InputFactory.Model(inputName); - InputModelType[] models = reverseOrder ? [shipped, widget] : [widget, shipped]; - await MockHelpers.LoadMockGeneratorAsync( - inputModelTypes: models, - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(), - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync( - parameters: "LastContract")); - - var providers = models.Select(m => CodeModelGenerator.Instance.TypeFactory.CreateModel(m)!).ToArray(); - - CollectionAssert.AreEquivalent(new[] { "WidgetResponse", "IPResponse" }, providers.Select(p => p.Name)); - } - [Test] public async Task TestBuildName_BackCompatTakesPrecedenceOverAcronymNormalization() { @@ -1208,7 +868,6 @@ public void TestBuildAdditionalProperties( "new global::System.Collections.ObjectModel.ReadOnlyDictionary Date: Wed, 23 Sep 2026 15:50:54 +0000 Subject: [PATCH 62/68] refactor(csharp): thread namespace through name normalization Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ClientProviderCustomizationTests.cs | 19 -------------- .../CustomWidgetClient.cs | 9 ------- .../src/Providers/ModelProvider.cs | 25 ++++++++++--------- .../src/Providers/TypeProvider.cs | 4 +-- 4 files changed, 15 insertions(+), 42 deletions(-) delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs index f54983213a9..bdf17516e28 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs @@ -17,25 +17,6 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ClientProvide { public class ClientProviderCustomizationTests { - [Test] - public async Task ModelDoesNotUseClientCustomization() - { - var inputModel = InputFactory.Model("WidgetResponse"); - var inputClient = InputFactory.Client("WidgetResult"); - inputClient.Update(@namespace: "Sample.Models"); - await MockHelpers.LoadMockGeneratorAsync( - inputModels: () => [inputModel], - clients: () => [inputClient], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - var modelProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; - var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; - - Assert.AreEqual("WidgetResult", modelProvider.Name); - Assert.IsNull(modelProvider.CustomCodeView); - Assert.AreEqual("CustomWidgetClient", clientProvider.CustomCodeView?.Name); - } - [Test] public async Task CanAddMethod() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs deleted file mode 100644 index e9e6c61e62b..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.TypeSpec.Generator.Customizations; - -namespace Sample.Models -{ - [CodeGenType("WidgetResult")] - public partial class CustomWidgetClient - { - } -} 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 c30241dbc8c..41d32795c8a 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 @@ -245,7 +245,10 @@ protected FieldProvider? RawDataField /// public ConstructorProvider FullConstructor => _fullConstructor ??= BuildFullConstructor(); - protected override string BuildNamespace() => GetTypeNamespace(_inputModel.Namespace); + protected override string BuildNamespace() => string.IsNullOrEmpty(_inputModel.Namespace) ? + // TODO remove null check once https://github.com/Azure/typespec-azure/issues/2209 is fixed. + CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace : + CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(_inputModel.Namespace); protected override CSharpType? BuildBaseType() { @@ -315,15 +318,14 @@ protected override string BuildName() return NormalizeTypeNameForNewContract(_inputModel.Name.ToIdentifierName()); } - private protected override string NormalizeTypeName(string name) + private protected override string NormalizeTypeName(string name, string typeNamespace) { - var normalizedName = base.NormalizeTypeName(name); + var normalizedName = base.NormalizeTypeName(name, typeNamespace); if (!normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) { return normalizedName; } - var typeNamespace = BuildNamespace(); var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; if (sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, normalizedName, DeclaringTypeName) is not null || sourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName, DeclaringTypeName) is not null) @@ -345,12 +347,6 @@ private protected override string NormalizeTypeName(string name) (name, ns) => base.BuildCustomCodeView(name, ns)); } - private static string GetTypeNamespace(string inputNamespace) - => string.IsNullOrEmpty(inputNamespace) - // TODO remove null check once https://github.com/Azure/typespec-azure/issues/2209 is fixed. - ? CodeModelGenerator.Instance.TypeFactory.PrimaryNamespace - : CodeModelGenerator.Instance.TypeFactory.GetCleanNameSpace(inputNamespace); - private protected override TypeProvider? BuildLastContractView(string? generatedTypeName = null, string? generatedTypeNamespace = null) { var typeNamespace = generatedTypeNamespace ?? CustomCodeView?.Type.Namespace ?? BuildNamespace(); @@ -381,8 +377,13 @@ private static string GetTypeNamespace(string inputNamespace) return null; } - return buildView(originalName, typeNamespace) ?? - (normalizedOriginalName == originalName ? null : buildView(normalizedOriginalName, typeNamespace)); + var customView = buildView(originalName, typeNamespace); + if (customView is not null || normalizedOriginalName == originalName) + { + return customView; + } + + return buildView(normalizedOriginalName, typeNamespace); } protected override TypeSignatureModifiers BuildDeclarationModifiers() diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs index 52e2fb28618..88aea5147fa 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs @@ -749,7 +749,7 @@ protected string NormalizeTypeNameForNewContract(string name) return name; } - var normalizedName = NormalizeTypeName(name); + var normalizedName = NormalizeTypeName(name, typeNamespace); if (normalizedName == name) { return name; @@ -762,7 +762,7 @@ protected string NormalizeTypeNameForNewContract(string name) return lastContractType is null ? normalizedName : name; } - private protected virtual string NormalizeTypeName(string name) => name.NormalizeCSharpAcronyms(); + private protected virtual string NormalizeTypeName(string name, string typeNamespace) => name.NormalizeCSharpAcronyms(); /// /// Resets only the cached methods so they are rebuilt on next access. From 3aa85210d5b51bb6b2be7b9927f5a10600d437d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:22:15 +0000 Subject: [PATCH 63/68] fix(csharp): scope CodeGenType aliases to their owning input type Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ClientProviderCustomizationTests.cs | 20 +++++++ .../CustomWidgetClient.cs | 9 +++ .../src/Providers/ModelProvider.cs | 55 +++++++++++++++---- .../src/SourceInput/SourceInputModel.cs | 21 +++++-- 4 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs index bdf17516e28..6b245839bf2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs @@ -17,6 +17,26 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ClientProvide { public class ClientProviderCustomizationTests { + [Test] + public async Task ModelDoesNotUseClientCustomization() + { + var inputModel = InputFactory.Model("WidgetResponse"); + var inputClient = InputFactory.Client("WidgetResult"); + inputClient.Update(@namespace: "Sample.Models"); + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => [inputModel], + clients: () => [inputClient], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; + var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; + + // The client customization is keyed on the client's own name, so the renamed model must not claim it. + Assert.AreEqual("WidgetResult", modelProvider.Name); + Assert.IsNull(modelProvider.CustomCodeView); + Assert.AreEqual("CustomWidgetClient", clientProvider.CustomCodeView?.Name); + } + [Test] public async Task CanAddMethod() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs new file mode 100644 index 00000000000..e9e6c61e62b --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs @@ -0,0 +1,9 @@ +using Microsoft.TypeSpec.Generator.Customizations; + +namespace Sample.Models +{ + [CodeGenType("WidgetResult")] + public partial class CustomWidgetClient + { + } +} 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 41d32795c8a..28269e88d60 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 @@ -340,7 +340,19 @@ private protected override string NormalizeTypeName(string name, string typeName { var typeNamespace = generatedTypeNamespace ?? BuildNamespace(); var typeName = generatedTypeName ?? BuildName(); - var customCodeView = base.BuildCustomCodeView(typeName, typeNamespace); + if (!IsTranslatedResultName(typeName)) + { + return base.BuildCustomCodeView(typeName, typeNamespace); + } + + // A CodeGenType alias names its target by the original name of that type, so the translated name + // may only consume an alias that no other input type already owns. Otherwise a customization + // written for a client that is genuinely named after the translated name would be attached here. + var customCodeView = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( + typeNamespace, + typeName, + DeclaringTypeName, + includeCodeGenTypeAliases: !IsNameOfInputClient(typeName)); return customCodeView ?? BuildResponseSuffixFallbackView( typeName, typeNamespace, @@ -363,21 +375,14 @@ private protected override string NormalizeTypeName(string name, string typeName string typeNamespace, Func buildView) { - if (_inputModel.IsExactName) + if (!IsTranslatedResultName(typeName)) { return null; } var originalName = _inputModel.Name.ToIdentifierName(); - var normalizedOriginalName = originalName.NormalizeCSharpAcronyms(); - if (!normalizedOriginalName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || - originalName == typeName || - typeName != $"{normalizedOriginalName[..^ResponseSuffix.Length]}Result") - { - return null; - } - var customView = buildView(originalName, typeNamespace); + var normalizedOriginalName = originalName.NormalizeCSharpAcronyms(); if (customView is not null || normalizedOriginalName == originalName) { return customView; @@ -386,6 +391,36 @@ private protected override string NormalizeTypeName(string name, string typeName return buildView(normalizedOriginalName, typeNamespace); } + private static bool IsNameOfInputClient(string typeName) + { + foreach (var client in CodeModelGenerator.Instance.InputLibrary.InputNamespace.Clients) + { + var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); + if (string.Equals(clientName, typeName, StringComparison.Ordinal)) + { + return true; + } + } + + return false; + } + + /// + /// Determines whether is the name this model receives from the + /// Response to Result suffix translation rather than its original spec name. + /// + private bool IsTranslatedResultName(string typeName) + { + if (_inputModel.IsExactName) + { + return false; + } + + var normalizedOriginalName = _inputModel.Name.ToIdentifierName().NormalizeCSharpAcronyms(); + return normalizedOriginalName.EndsWith(ResponseSuffix, StringComparison.Ordinal) && + typeName == $"{normalizedOriginalName[..^ResponseSuffix.Length]}Result"; + } + protected override TypeSignatureModifiers BuildDeclarationModifiers() { var customCodeModifiers = CustomCodeView?.DeclarationModifiers ?? TypeSignatureModifiers.None; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs index 18ed06828ff..9401155439c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs @@ -62,9 +62,20 @@ private IReadOnlyDictionary PopulateNameMap() return nameMap; } - public TypeProvider? FindForTypeInCurrentCompilation(string ns, string name, string? declaringTypeName = null, bool includeReferencedAssemblies = false) + /// + /// Whether a customization that declares CodeGenType with as its original + /// name may be returned. Pass false when is a name the generator translated + /// rather than the original name of the type, since CodeGenType aliases are keyed on original names + /// and would otherwise attach another type's customization to the caller. + /// + public TypeProvider? FindForTypeInCurrentCompilation( + string ns, + string name, + string? declaringTypeName = null, + bool includeReferencedAssemblies = false, + bool includeCodeGenTypeAliases = true) { - return FindTypeInCompilation(Customization, ns, name, includeReferencedAssemblies, declaringTypeName); + return FindTypeInCompilation(Customization, ns, name, includeReferencedAssemblies, declaringTypeName, includeCodeGenTypeAliases: includeCodeGenTypeAliases); } public TypeProvider? FindForTypeInLastContract(string ns, string name, string? declaringTypeName = null) @@ -112,7 +123,8 @@ private static string GetFullyQualifiedMetadataName(string ns, string name, stri string name, bool includeReferencedAssemblies, string? declaringTypeName = null, - bool includeInternal = true) + bool includeInternal = true, + bool includeCodeGenTypeAliases = true) { if (compilation == null) { @@ -123,7 +135,8 @@ private static string GetFullyQualifiedMetadataName(string ns, string name, stri // Either find by the CodeGenType attribute in customization or by the actual type name. INamedTypeSymbol? type = null; - if (ReferenceEquals(compilation, Customization) && + if (includeCodeGenTypeAliases && + ReferenceEquals(compilation, Customization) && _nameMap.Value.TryGetValue(name, out var mappedType) && IsContainingTypeMatch(mappedType, ns, declaringTypeName)) { From 7623f298edd8523fc7a7bc6283ff833282291989 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:23:41 +0000 Subject: [PATCH 64/68] docs(csharp): explain client-only customization ownership scope Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 6 ++++++ 1 file changed, 6 insertions(+) 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 28269e88d60..6974fffd253 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 @@ -391,6 +391,12 @@ private protected override string NormalizeTypeName(string name, string typeName return buildView(normalizedOriginalName, typeNamespace); } + /// + /// Determines whether an input client is named . Only clients are + /// considered: a client and a model of the same name coexist today, so the suffix translation is + /// what puts this model in contention for the client's customization. A model or enum of the same + /// name is instead a duplicate the spec already produces, which the generator does not arbitrate. + /// private static bool IsNameOfInputClient(string typeName) { foreach (var client in CodeModelGenerator.Instance.InputLibrary.InputNamespace.Clients) From 2b77efb0887f52aabb69f914cc694a2ff09089df Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:44:01 +0000 Subject: [PATCH 65/68] revert(csharp): restore BuildNamespace use in NormalizeTypeName Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 5 +++-- .../src/Providers/TypeProvider.cs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) 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 6974fffd253..98afe2989c9 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 @@ -318,14 +318,15 @@ protected override string BuildName() return NormalizeTypeNameForNewContract(_inputModel.Name.ToIdentifierName()); } - private protected override string NormalizeTypeName(string name, string typeNamespace) + private protected override string NormalizeTypeName(string name) { - var normalizedName = base.NormalizeTypeName(name, typeNamespace); + var normalizedName = base.NormalizeTypeName(name); if (!normalizedName.EndsWith(ResponseSuffix, StringComparison.Ordinal)) { return normalizedName; } + var typeNamespace = BuildNamespace(); var sourceInputModel = CodeModelGenerator.Instance.SourceInputModel; if (sourceInputModel.FindForTypeInCurrentCompilation(typeNamespace, normalizedName, DeclaringTypeName) is not null || sourceInputModel.FindForTypeInLastContract(typeNamespace, normalizedName, DeclaringTypeName) is not null) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs index 88aea5147fa..52e2fb28618 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs @@ -749,7 +749,7 @@ protected string NormalizeTypeNameForNewContract(string name) return name; } - var normalizedName = NormalizeTypeName(name, typeNamespace); + var normalizedName = NormalizeTypeName(name); if (normalizedName == name) { return name; @@ -762,7 +762,7 @@ protected string NormalizeTypeNameForNewContract(string name) return lastContractType is null ? normalizedName : name; } - private protected virtual string NormalizeTypeName(string name, string typeNamespace) => name.NormalizeCSharpAcronyms(); + private protected virtual string NormalizeTypeName(string name) => name.NormalizeCSharpAcronyms(); /// /// Resets only the cached methods so they are rebuilt on next access. From 72e21bbdf8ff2177f68d075ad88a8e1810b28c05 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Sep 2026 17:02:29 +0000 Subject: [PATCH 66/68] refactor(csharp): simplify client customization lookup Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelProvider.cs | 22 ++++++++++--------- .../src/SourceInput/SourceInputModel.cs | 17 ++++---------- 2 files changed, 16 insertions(+), 23 deletions(-) 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 98afe2989c9..a4566233459 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 @@ -346,14 +346,15 @@ private protected override string NormalizeTypeName(string name) return base.BuildCustomCodeView(typeName, typeNamespace); } - // A CodeGenType alias names its target by the original name of that type, so the translated name - // may only consume an alias that no other input type already owns. Otherwise a customization - // written for a client that is genuinely named after the translated name would be attached here. - var customCodeView = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( - typeNamespace, - typeName, - DeclaringTypeName, - includeCodeGenTypeAliases: !IsNameOfInputClient(typeName)); + TypeProvider? customCodeView = null; + if (!IsNameOfInputClient(typeNamespace, typeName)) + { + customCodeView = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( + typeNamespace, + typeName, + DeclaringTypeName); + } + return customCodeView ?? BuildResponseSuffixFallbackView( typeName, typeNamespace, @@ -398,12 +399,13 @@ private protected override string NormalizeTypeName(string name) /// what puts this model in contention for the client's customization. A model or enum of the same /// name is instead a duplicate the spec already produces, which the generator does not arbitrate. /// - private static bool IsNameOfInputClient(string typeName) + private static bool IsNameOfInputClient(string typeNamespace, string typeName) { foreach (var client in CodeModelGenerator.Instance.InputLibrary.InputNamespace.Clients) { var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - if (string.Equals(clientName, typeName, StringComparison.Ordinal)) + if (string.Equals(client.Namespace, typeNamespace, StringComparison.Ordinal) && + string.Equals(clientName, typeName, StringComparison.Ordinal)) { return true; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs index 9401155439c..6cb742887eb 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs @@ -62,20 +62,13 @@ private IReadOnlyDictionary PopulateNameMap() return nameMap; } - /// - /// Whether a customization that declares CodeGenType with as its original - /// name may be returned. Pass false when is a name the generator translated - /// rather than the original name of the type, since CodeGenType aliases are keyed on original names - /// and would otherwise attach another type's customization to the caller. - /// public TypeProvider? FindForTypeInCurrentCompilation( string ns, string name, string? declaringTypeName = null, - bool includeReferencedAssemblies = false, - bool includeCodeGenTypeAliases = true) + bool includeReferencedAssemblies = false) { - return FindTypeInCompilation(Customization, ns, name, includeReferencedAssemblies, declaringTypeName, includeCodeGenTypeAliases: includeCodeGenTypeAliases); + return FindTypeInCompilation(Customization, ns, name, includeReferencedAssemblies, declaringTypeName); } public TypeProvider? FindForTypeInLastContract(string ns, string name, string? declaringTypeName = null) @@ -123,8 +116,7 @@ private static string GetFullyQualifiedMetadataName(string ns, string name, stri string name, bool includeReferencedAssemblies, string? declaringTypeName = null, - bool includeInternal = true, - bool includeCodeGenTypeAliases = true) + bool includeInternal = true) { if (compilation == null) { @@ -135,8 +127,7 @@ private static string GetFullyQualifiedMetadataName(string ns, string name, stri // Either find by the CodeGenType attribute in customization or by the actual type name. INamedTypeSymbol? type = null; - if (includeCodeGenTypeAliases && - ReferenceEquals(compilation, Customization) && + if (ReferenceEquals(compilation, Customization) && _nameMap.Value.TryGetValue(name, out var mappedType) && IsContainingTypeMatch(mappedType, ns, declaringTypeName)) { From 6fdba6f54f2a2a21fd731a921ece3d1bf6076a26 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Sep 2026 17:10:18 +0000 Subject: [PATCH 67/68] refactor(csharp): remove client customization guard Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ClientProviderCustomizationTests.cs | 20 ------ .../CustomWidgetClient.cs | 9 --- .../src/Providers/ModelProvider.cs | 64 +++---------------- 3 files changed, 8 insertions(+), 85 deletions(-) delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs index 6b245839bf2..bdf17516e28 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/ClientProviderCustomizationTests.cs @@ -17,26 +17,6 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.ClientProvide { public class ClientProviderCustomizationTests { - [Test] - public async Task ModelDoesNotUseClientCustomization() - { - var inputModel = InputFactory.Model("WidgetResponse"); - var inputClient = InputFactory.Client("WidgetResult"); - inputClient.Update(@namespace: "Sample.Models"); - await MockHelpers.LoadMockGeneratorAsync( - inputModels: () => [inputModel], - clients: () => [inputClient], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - var modelProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel)!; - var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(inputClient)!; - - // The client customization is keyed on the client's own name, so the renamed model must not claim it. - Assert.AreEqual("WidgetResult", modelProvider.Name); - Assert.IsNull(modelProvider.CustomCodeView); - Assert.AreEqual("CustomWidgetClient", clientProvider.CustomCodeView?.Name); - } - [Test] public async Task CanAddMethod() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs deleted file mode 100644 index e9e6c61e62b..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderCustomizationTests/ModelDoesNotUseClientCustomization/CustomWidgetClient.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.TypeSpec.Generator.Customizations; - -namespace Sample.Models -{ - [CodeGenType("WidgetResult")] - public partial class CustomWidgetClient - { - } -} 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 a4566233459..f9c1c0eee6e 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 @@ -341,20 +341,7 @@ private protected override string NormalizeTypeName(string name) { var typeNamespace = generatedTypeNamespace ?? BuildNamespace(); var typeName = generatedTypeName ?? BuildName(); - if (!IsTranslatedResultName(typeName)) - { - return base.BuildCustomCodeView(typeName, typeNamespace); - } - - TypeProvider? customCodeView = null; - if (!IsNameOfInputClient(typeNamespace, typeName)) - { - customCodeView = CodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( - typeNamespace, - typeName, - DeclaringTypeName); - } - + var customCodeView = base.BuildCustomCodeView(typeName, typeNamespace); return customCodeView ?? BuildResponseSuffixFallbackView( typeName, typeNamespace, @@ -377,57 +364,22 @@ private protected override string NormalizeTypeName(string name) string typeNamespace, Func buildView) { - if (!IsTranslatedResultName(typeName)) + if (_inputModel.IsExactName) { return null; } var originalName = _inputModel.Name.ToIdentifierName(); - var customView = buildView(originalName, typeNamespace); var normalizedOriginalName = originalName.NormalizeCSharpAcronyms(); - if (customView is not null || normalizedOriginalName == originalName) - { - return customView; - } - - return buildView(normalizedOriginalName, typeNamespace); - } - - /// - /// Determines whether an input client is named . Only clients are - /// considered: a client and a model of the same name coexist today, so the suffix translation is - /// what puts this model in contention for the client's customization. A model or enum of the same - /// name is instead a duplicate the spec already produces, which the generator does not arbitrate. - /// - private static bool IsNameOfInputClient(string typeNamespace, string typeName) - { - foreach (var client in CodeModelGenerator.Instance.InputLibrary.InputNamespace.Clients) + if (!normalizedOriginalName.EndsWith(ResponseSuffix, StringComparison.Ordinal) || + originalName == typeName || + typeName != $"{normalizedOriginalName[..^ResponseSuffix.Length]}Result") { - var clientName = client.IsExactName ? client.Name : client.Name.ToIdentifierName(); - if (string.Equals(client.Namespace, typeNamespace, StringComparison.Ordinal) && - string.Equals(clientName, typeName, StringComparison.Ordinal)) - { - return true; - } - } - - return false; - } - - /// - /// Determines whether is the name this model receives from the - /// Response to Result suffix translation rather than its original spec name. - /// - private bool IsTranslatedResultName(string typeName) - { - if (_inputModel.IsExactName) - { - return false; + return null; } - var normalizedOriginalName = _inputModel.Name.ToIdentifierName().NormalizeCSharpAcronyms(); - return normalizedOriginalName.EndsWith(ResponseSuffix, StringComparison.Ordinal) && - typeName == $"{normalizedOriginalName[..^ResponseSuffix.Length]}Result"; + return buildView(originalName, typeNamespace) ?? + (normalizedOriginalName == originalName ? null : buildView(normalizedOriginalName, typeNamespace)); } protected override TypeSignatureModifiers BuildDeclarationModifiers() From 257f96daad081e65af111a18d422a28312bc0d5c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 23 Sep 2026 17:46:17 +0000 Subject: [PATCH 68/68] revert(csharp): restore SourceInputModel lookup Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/SourceInput/SourceInputModel.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs index 6cb742887eb..18ed06828ff 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/SourceInputModel.cs @@ -62,11 +62,7 @@ private IReadOnlyDictionary PopulateNameMap() return nameMap; } - public TypeProvider? FindForTypeInCurrentCompilation( - string ns, - string name, - string? declaringTypeName = null, - bool includeReferencedAssemblies = false) + public TypeProvider? FindForTypeInCurrentCompilation(string ns, string name, string? declaringTypeName = null, bool includeReferencedAssemblies = false) { return FindTypeInCompilation(Customization, ns, name, includeReferencedAssemblies, declaringTypeName); }