From e0f7698d94346f9a674f43deef0f79ab8c5d21b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:52:43 +0000 Subject: [PATCH 01/10] Initial plan From 52ffd9c87de3cdfa36861036c342ad2afa21967e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:59:59 +0000 Subject: [PATCH 02/10] fix(http-client-csharp): exclude non-public model context attributes Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ModelReaderWriterContextDefinition.cs | 13 +++ ...ModelReaderWriterContextDefinitionTests.cs | 89 ++++++++++++++++++- .../Models.cs | 13 +++ .../Models.cs | 9 ++ .../SampleContext.cs | 29 ++++++ 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectCustomizedAccessibility/Models.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs index 6e2a7ad9a51..92b563cb7e1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs @@ -602,6 +602,14 @@ private static void AddAttributeForType( TypeProvider typeProvider, string key) { + for (var provider = typeProvider; provider != null; provider = provider.DeclaringTypeProvider) + { + if (!provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) + { + return; + } + } + AttributeStatement? experimentalOrObsoleteAttribute = typeProvider.CanonicalView.Attributes .FirstOrDefault(a => a.Type.Equals(typeof(ExperimentalAttribute)) || a.Type.Equals(typeof(ObsoleteAttribute))); @@ -628,6 +636,11 @@ private static void AddAttributeForType( string experimentalTypeJustification, string obsoleteTypeJustification) { + if (!frameworkType.IsVisible) + { + return; + } + var key = frameworkType.FullName ?? frameworkType.Name; // Match [Experimental] by attribute type full name rather than runtime identity. Dependencies that diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs index 83e92558663..d89e421e3fa 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs @@ -75,6 +75,93 @@ public void ValidateModelReaderWriterBuildableAttributesAreGenerated() Assert.AreEqual(1, buildableAttributes.Count(), "Exactly one ModelReaderWriterBuildableAttribute should be generated for TestModel"); } + [TestCase("public", 1)] + [TestCase("internal", 0)] + public void BuildableAttributesRespectModelAccessibility(string access, int expectedCount) + { + MockHelpers.LoadMockGenerator( + inputModels: () => [InputFactory.Model("TestModel", access: access)]); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + + Assert.AreEqual(expectedCount, GetBuildableAttributes(contextDefinition).Count); + } + + [Test] + public async Task BuildableAttributesRespectCustomizedAccessibility() + { + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => + [ + InputFactory.Model("InternalModel"), + InputFactory.Model("PublicModel", access: "internal") + ], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + Assert.AreEqual(1, buildableAttributes.Count); + Assert.AreEqual("typeof(global::Sample.Models.PublicModel)", buildableAttributes[0].Arguments[0].ToDisplayString()); + } + + [TestCase(TypeSignatureModifiers.Public, 2)] + [TestCase(TypeSignatureModifiers.Internal, 1)] + [TestCase(TypeSignatureModifiers.Private, 1)] + [TestCase(TypeSignatureModifiers.Protected, 1)] + [TestCase(TypeSignatureModifiers.Protected | TypeSignatureModifiers.Internal, 1)] + [TestCase(TypeSignatureModifiers.Private | TypeSignatureModifiers.Protected, 1)] + public void BuildableAttributesRespectUpdatedAccessibility(TypeSignatureModifiers accessibility, int expectedCount) + { + var provider = new TestMrwSerialization(implementsPersistableModel: true, includeDepModelProperty: true); + MockHelpers.LoadMockGenerator(createOutputLibrary: () => new TestOutputLibrary([provider])); + + _ = provider.Type; + provider.Update(modifiers: accessibility | TypeSignatureModifiers.Class); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + Assert.AreEqual(expectedCount, buildableAttributes.Count); + Assert.AreEqual( + "typeof(global::Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions.ModelReaderWriterContextDefinitionTests.DependencyModel)", + buildableAttributes[0].Arguments[0].ToDisplayString()); + } + + [Test] + public void NonPublicFrameworkTypesDoNotContributeBuildableAttributes() + { + MockHelpers.LoadMockGenerator( + inputModels: () => [InputFactory.Model("PublicModel")], + createCSharpTypeCore: _ => new CSharpType(typeof(TestInternalType)), + createCSharpTypeCoreFallback: input => input == InputPrimitiveType.String); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + Assert.AreEqual(1, buildableAttributes.Count); + Assert.AreEqual("typeof(global::Sample.Models.PublicModel)", buildableAttributes[0].Arguments[0].ToDisplayString()); + } + + [Test] + public async Task NonPublicTypesAreNotRestoredFromLastContract() + { + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => + [ + InputFactory.Model("InternalModel", access: "internal"), + InputFactory.Model("PublicModel") + ], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + Assert.AreEqual(1, buildableAttributes.Count); + Assert.AreEqual("typeof(global::Sample.Models.PublicModel)", buildableAttributes[0].Arguments[0].ToDisplayString()); + } + [TestCase(true)] [TestCase(false)] public void ValidateModelReaderWriterBuildableAttributesAreGeneratedForNonModelsThatImplementMRW(bool implementsIPersistable) @@ -1198,7 +1285,7 @@ BinaryData IPersistableModel.Write(ModelReaderWriterOptions opt } [Experimental("TEST001")] - private class ExperimentalDependencyModel : IJsonModel + public class ExperimentalDependencyModel : IJsonModel { ExperimentalDependencyModel? IJsonModel.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectCustomizedAccessibility/Models.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectCustomizedAccessibility/Models.cs new file mode 100644 index 00000000000..6601dfb28cc --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectCustomizedAccessibility/Models.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Sample.Models +{ + internal partial class InternalModel + { + } + + public partial class PublicModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs new file mode 100644 index 00000000000..5e5790488db --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Sample.Models +{ + internal class InternalCustomModel + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs new file mode 100644 index 00000000000..d613e3fb4ff --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Models.InternalModel))] + [ModelReaderWriterBuildable(typeof(Models.PublicModel))] + [ModelReaderWriterBuildable(typeof(Models.InternalCustomModel))] + public partial class SampleContext : ModelReaderWriterContext + { + } +} + +namespace Sample.Models +{ + public class InternalModel + { + } + + public class PublicModel + { + } + + public class InternalCustomModel + { + } +} From bc8a9880b2353273f8a8fe8e949d0effd5a6acbd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 15:09:27 +0000 Subject: [PATCH 03/10] fix(http-client-csharp): handle nested accessibility and refresh contexts Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ModelReaderWriterContextDefinition.cs | 21 ++++++++++++++++++- ...ModelReaderWriterContextDefinitionTests.cs | 9 ++++++-- .../Models.cs | 14 +++++++++++++ .../SampleContext.cs | 16 ++++++++++++++ .../Generated/Models/SampleTypeSpecContext.cs | 8 ------- ...odelInheritanceEnumDiscriminatorContext.cs | 2 -- ...elInheritanceNestedDiscriminatorContext.cs | 2 -- ...elInheritanceSingleDiscriminatorContext.cs | 3 --- ...TypePropertyAdditionalPropertiesContext.cs | 2 -- 9 files changed, 57 insertions(+), 20 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs index 92b563cb7e1..cab852d6000 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs @@ -189,7 +189,7 @@ private void AddLastContractBuildableAttributes( null, includeReferencedAssemblies: true); - if (resolvedProvider is null) + if (resolvedProvider is null || !HasPublicDeclaringTypes(targetType)) { continue; } @@ -215,6 +215,25 @@ private void AddLastContractBuildableAttributes( } } + private static bool HasPublicDeclaringTypes(CSharpType type) + { + for (var declaringType = type.DeclaringType; declaringType != null; declaringType = declaringType.DeclaringType) + { + var provider = ScmCodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( + declaringType.Namespace, + declaringType.ClrMetadataName, + null, + includeReferencedAssemblies: true); + + if (provider?.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) != true) + { + return false; + } + } + + return true; + } + private static CSharpType? GetBuildableAttributeTargetType(AttributeStatement attribute) { foreach (var argument in attribute.Arguments) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs index d89e421e3fa..82d1314192c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs @@ -158,8 +158,13 @@ await MockHelpers.LoadMockGeneratorAsync( var contextDefinition = new ModelReaderWriterContextDefinition(); var buildableAttributes = GetBuildableAttributes(contextDefinition); - Assert.AreEqual(1, buildableAttributes.Count); - Assert.AreEqual("typeof(global::Sample.Models.PublicModel)", buildableAttributes[0].Arguments[0].ToDisplayString()); + CollectionAssert.AreEquivalent( + new[] + { + "typeof(global::Sample.Models.PublicModel)", + "typeof(global::Sample.Models.PublicContainer.NestedModel)" + }, + buildableAttributes.Select(a => a.Arguments[0].ToDisplayString())); } [TestCase(true)] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs index 5e5790488db..dda1188439f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs @@ -6,4 +6,18 @@ namespace Sample.Models internal class InternalCustomModel { } + + internal class InternalContainer + { + public class NestedModel + { + } + } + + public class PublicContainer + { + public class NestedModel + { + } + } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs index d613e3fb4ff..5a2862beb0b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs @@ -8,6 +8,8 @@ namespace Sample [ModelReaderWriterBuildable(typeof(Models.InternalModel))] [ModelReaderWriterBuildable(typeof(Models.PublicModel))] [ModelReaderWriterBuildable(typeof(Models.InternalCustomModel))] + [ModelReaderWriterBuildable(typeof(Models.InternalContainer.NestedModel))] + [ModelReaderWriterBuildable(typeof(Models.PublicContainer.NestedModel))] public partial class SampleContext : ModelReaderWriterContext { } @@ -26,4 +28,18 @@ public class PublicModel public class InternalCustomModel { } + + public class InternalContainer + { + public class NestedModel + { + } + } + + public class PublicContainer + { + public class NestedModel + { + } + } } 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 7cf48210f0d..772b0ecc8f3 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 @@ -21,14 +21,9 @@ namespace SampleTypeSpec [ModelReaderWriterBuildable(typeof(Friend))] [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))] - [ModelReaderWriterBuildable(typeof(PageThing))] [ModelReaderWriterBuildable(typeof(Pet))] [ModelReaderWriterBuildable(typeof(Plant))] [ModelReaderWriterBuildable(typeof(RenamedModelCustom))] @@ -37,9 +32,6 @@ namespace SampleTypeSpec [ModelReaderWriterBuildable(typeof(StreamingItem))] [ModelReaderWriterBuildable(typeof(Thing))] [ModelReaderWriterBuildable(typeof(Tree))] - [ModelReaderWriterBuildable(typeof(UnknownAnimal))] - [ModelReaderWriterBuildable(typeof(UnknownPet))] - [ModelReaderWriterBuildable(typeof(UnknownPlant))] [ModelReaderWriterBuildable(typeof(Wrapper))] [ModelReaderWriterBuildable(typeof(XmlAdvancedModel))] [ModelReaderWriterBuildable(typeof(XmlItem))] diff --git a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/enum-discriminator/src/Generated/Models/_TypeModelInheritanceEnumDiscriminatorContext.cs b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/enum-discriminator/src/Generated/Models/_TypeModelInheritanceEnumDiscriminatorContext.cs index b495b0c63ff..451301276ed 100644 --- a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/enum-discriminator/src/Generated/Models/_TypeModelInheritanceEnumDiscriminatorContext.cs +++ b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/enum-discriminator/src/Generated/Models/_TypeModelInheritanceEnumDiscriminatorContext.cs @@ -10,8 +10,6 @@ namespace _Type.Model.Inheritance.EnumDiscriminator [ModelReaderWriterBuildable(typeof(Dog))] [ModelReaderWriterBuildable(typeof(Golden))] [ModelReaderWriterBuildable(typeof(Snake))] - [ModelReaderWriterBuildable(typeof(UnknownDog))] - [ModelReaderWriterBuildable(typeof(UnknownSnake))] public partial class _TypeModelInheritanceEnumDiscriminatorContext : ModelReaderWriterContext { } diff --git a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/nested-discriminator/src/Generated/Models/_TypeModelInheritanceNestedDiscriminatorContext.cs b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/nested-discriminator/src/Generated/Models/_TypeModelInheritanceNestedDiscriminatorContext.cs index f81c0d9cc5c..116c20821d6 100644 --- a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/nested-discriminator/src/Generated/Models/_TypeModelInheritanceNestedDiscriminatorContext.cs +++ b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/nested-discriminator/src/Generated/Models/_TypeModelInheritanceNestedDiscriminatorContext.cs @@ -11,8 +11,6 @@ namespace _Type.Model.Inheritance.NestedDiscriminator [ModelReaderWriterBuildable(typeof(Salmon))] [ModelReaderWriterBuildable(typeof(SawShark))] [ModelReaderWriterBuildable(typeof(Shark))] - [ModelReaderWriterBuildable(typeof(UnknownFish))] - [ModelReaderWriterBuildable(typeof(UnknownShark))] public partial class _TypeModelInheritanceNestedDiscriminatorContext : ModelReaderWriterContext { } diff --git a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/single-discriminator/src/Generated/Models/_TypeModelInheritanceSingleDiscriminatorContext.cs b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/single-discriminator/src/Generated/Models/_TypeModelInheritanceSingleDiscriminatorContext.cs index be4653744b5..d3d4bdbe9f7 100644 --- a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/single-discriminator/src/Generated/Models/_TypeModelInheritanceSingleDiscriminatorContext.cs +++ b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/model/inheritance/single-discriminator/src/Generated/Models/_TypeModelInheritanceSingleDiscriminatorContext.cs @@ -14,9 +14,6 @@ namespace _Type.Model.Inheritance.SingleDiscriminator [ModelReaderWriterBuildable(typeof(SeaGull))] [ModelReaderWriterBuildable(typeof(Sparrow))] [ModelReaderWriterBuildable(typeof(TRex))] - [ModelReaderWriterBuildable(typeof(UnknownBird))] - [ModelReaderWriterBuildable(typeof(UnknownDinosaur))] - [ModelReaderWriterBuildable(typeof(UnknownFish))] public partial class _TypeModelInheritanceSingleDiscriminatorContext : ModelReaderWriterContext { } diff --git a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/property/additional-properties/src/Generated/Models/_TypePropertyAdditionalPropertiesContext.cs b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/property/additional-properties/src/Generated/Models/_TypePropertyAdditionalPropertiesContext.cs index ad3172fc71e..8ef1daa1d9e 100644 --- a/packages/http-client-csharp/generator/TestProjects/Spector/http/type/property/additional-properties/src/Generated/Models/_TypePropertyAdditionalPropertiesContext.cs +++ b/packages/http-client-csharp/generator/TestProjects/Spector/http/type/property/additional-properties/src/Generated/Models/_TypePropertyAdditionalPropertiesContext.cs @@ -40,8 +40,6 @@ namespace _Type.Property.AdditionalProperties [ModelReaderWriterBuildable(typeof(SpreadRecordForNonDiscriminatedUnion3))] [ModelReaderWriterBuildable(typeof(SpreadRecordForUnion))] [ModelReaderWriterBuildable(typeof(SpreadStringRecord))] - [ModelReaderWriterBuildable(typeof(UnknownExtendsUnknownAdditionalPropertiesDiscriminated))] - [ModelReaderWriterBuildable(typeof(UnknownIsUnknownAdditionalPropertiesDiscriminated))] [ModelReaderWriterBuildable(typeof(WidgetData0))] [ModelReaderWriterBuildable(typeof(WidgetData1))] [ModelReaderWriterBuildable(typeof(WidgetData2))] From 34d2609735ea92b1ab9eb4181da6eb2561d4845f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 15:29:51 +0000 Subject: [PATCH 04/10] refactor(http-client-csharp): check public API before building context attributes Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ModelReaderWriterContextDefinition.cs | 24 ++++--------- .../src/Shared/MethodSignatureHelper.cs | 13 +++++++ .../test/Shared/MethodSignatureHelperTests.cs | 36 +++++++++++++++++++ 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs index cab852d6000..b80c0bf7ec9 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs @@ -49,7 +49,7 @@ protected override IReadOnlyList BuildAttributes() (HashSet buildableTypes, HashSet buildableProviders) = CollectBuildableTypes(); foreach (var type in buildableTypes) { - if (customizedBuildableTypes.Contains(GetTypeIdentity(type))) + if (!type.FrameworkType.IsVisible || customizedBuildableTypes.Contains(GetTypeIdentity(type))) { continue; } @@ -68,7 +68,9 @@ protected override IReadOnlyList BuildAttributes() } foreach (var provider in buildableProviders) { - if (!ShouldWriteProvider(provider) || customizedBuildableTypes.Contains(GetTypeIdentity(provider.Type))) + if (!ShouldWriteProvider(provider) + || !MethodSignatureHelper.IsPublicApi(provider) + || customizedBuildableTypes.Contains(GetTypeIdentity(provider.Type))) { continue; } @@ -195,7 +197,8 @@ private void AddLastContractBuildableAttributes( } } - if (resolvedProvider.CanonicalView.Attributes.Any(a => a.Type.Equals(typeof(ObsoleteAttribute)))) + if (!MethodSignatureHelper.IsPublicApi(resolvedProvider) + || resolvedProvider.CanonicalView.Attributes.Any(a => a.Type.Equals(typeof(ObsoleteAttribute)))) { continue; } @@ -225,7 +228,7 @@ private static bool HasPublicDeclaringTypes(CSharpType type) null, includeReferencedAssemblies: true); - if (provider?.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) != true) + if (provider is null || !MethodSignatureHelper.IsPublicApi(provider)) { return false; } @@ -621,14 +624,6 @@ private static void AddAttributeForType( TypeProvider typeProvider, string key) { - for (var provider = typeProvider; provider != null; provider = provider.DeclaringTypeProvider) - { - if (!provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) - { - return; - } - } - AttributeStatement? experimentalOrObsoleteAttribute = typeProvider.CanonicalView.Attributes .FirstOrDefault(a => a.Type.Equals(typeof(ExperimentalAttribute)) || a.Type.Equals(typeof(ObsoleteAttribute))); @@ -655,11 +650,6 @@ private static void AddAttributeForType( string experimentalTypeJustification, string obsoleteTypeJustification) { - if (!frameworkType.IsVisible) - { - return; - } - var key = frameworkType.FullName ?? frameworkType.Name; // Match [Experimental] by attribute type full name rather than runtime identity. Dependencies that diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs index 5ea3e2058a2..4b0a81f2d4f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs @@ -19,6 +19,19 @@ internal static bool IsPublicApi(MethodSignatureModifiers modifiers) => (modifiers.HasFlag(MethodSignatureModifiers.Public) || modifiers.HasFlag(MethodSignatureModifiers.Protected)) && !modifiers.HasFlag(MethodSignatureModifiers.Private); + internal static bool IsPublicApi(TypeProvider typeProvider) + { + for (var provider = typeProvider; provider != null; provider = provider.DeclaringTypeProvider) + { + if (!provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) + { + return false; + } + } + + return true; + } + internal static bool ContainsSameParameters(MethodSignature method1, MethodSignature method2) { var count = method1.Parameters.Count; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs index fb999456636..d5ca40328bb 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs @@ -9,6 +9,8 @@ using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Statements; using Microsoft.TypeSpec.Generator.Tests.Common; +using Moq; +using Moq.Protected; using NUnit.Framework; using static Microsoft.TypeSpec.Generator.Snippets.Snippet; @@ -22,6 +24,40 @@ public void Setup() MockHelpers.LoadMockGenerator(); } + [TestCase(TypeSignatureModifiers.Public, true)] + [TestCase(TypeSignatureModifiers.Internal, false)] + [TestCase(TypeSignatureModifiers.Private, false)] + [TestCase(TypeSignatureModifiers.Protected, false)] + [TestCase(TypeSignatureModifiers.Protected | TypeSignatureModifiers.Internal, false)] + [TestCase(TypeSignatureModifiers.Private | TypeSignatureModifiers.Protected, false)] + public void IsPublicApiChecksTypeAccessibility(TypeSignatureModifiers accessibility, bool expected) + { + var provider = new Mock(); + provider.Object.Update(modifiers: accessibility | TypeSignatureModifiers.Class); + + Assert.AreEqual(expected, MethodSignatureHelper.IsPublicApi(provider.Object)); + } + + [TestCase(TypeSignatureModifiers.Public, true)] + [TestCase(TypeSignatureModifiers.Internal, false)] + [TestCase(TypeSignatureModifiers.Private, false)] + [TestCase(TypeSignatureModifiers.Protected, false)] + [TestCase(TypeSignatureModifiers.Protected | TypeSignatureModifiers.Internal, false)] + [TestCase(TypeSignatureModifiers.Private | TypeSignatureModifiers.Protected, false)] + public void IsPublicApiChecksAllDeclaringTypes(TypeSignatureModifiers accessibility, bool expected) + { + var outer = new Mock(); + outer.Object.Update(modifiers: accessibility | TypeSignatureModifiers.Class); + var middle = new Mock(); + middle.Object.Update(modifiers: TypeSignatureModifiers.Public | TypeSignatureModifiers.Class); + middle.Protected().Setup("BuildDeclaringTypeProvider").Returns(outer.Object); + var inner = new Mock(); + inner.Object.Update(modifiers: TypeSignatureModifiers.Public | TypeSignatureModifiers.Class); + inner.Protected().Setup("BuildDeclaringTypeProvider").Returns(middle.Object); + + Assert.AreEqual(expected, MethodSignatureHelper.IsPublicApi(inner.Object)); + } + [Test] public void ContainsSameParameters_SameParametersInSameOrder_ReturnsTrue() { From a04bb9c999e19c31d03d72d617c9f3a63e3b32e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 15:57:20 +0000 Subject: [PATCH 05/10] refactor(http-client-csharp): address context visibility review Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ModelReaderWriterContextDefinition.cs | 28 ++---------- ...ModelReaderWriterContextDefinitionTests.cs | 29 +----------- ...ttributesRespectCustomizedAccessibility.cs | 14 ++++++ .../Models.cs | 23 ---------- .../SampleContext.cs | 45 ------------------- .../src/Shared/MethodSignatureHelper.cs | 13 ------ .../test/Shared/MethodSignatureHelperTests.cs | 36 --------------- 7 files changed, 20 insertions(+), 168 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectCustomizedAccessibility.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs index b80c0bf7ec9..500b917e108 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs @@ -68,8 +68,8 @@ protected override IReadOnlyList BuildAttributes() } foreach (var provider in buildableProviders) { - if (!ShouldWriteProvider(provider) - || !MethodSignatureHelper.IsPublicApi(provider) + if (!provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) + || !ShouldWriteProvider(provider) || customizedBuildableTypes.Contains(GetTypeIdentity(provider.Type))) { continue; @@ -191,14 +191,13 @@ private void AddLastContractBuildableAttributes( null, includeReferencedAssemblies: true); - if (resolvedProvider is null || !HasPublicDeclaringTypes(targetType)) + if (resolvedProvider is null) { continue; } } - if (!MethodSignatureHelper.IsPublicApi(resolvedProvider) - || resolvedProvider.CanonicalView.Attributes.Any(a => a.Type.Equals(typeof(ObsoleteAttribute)))) + if (resolvedProvider.CanonicalView.Attributes.Any(a => a.Type.Equals(typeof(ObsoleteAttribute)))) { continue; } @@ -218,25 +217,6 @@ private void AddLastContractBuildableAttributes( } } - private static bool HasPublicDeclaringTypes(CSharpType type) - { - for (var declaringType = type.DeclaringType; declaringType != null; declaringType = declaringType.DeclaringType) - { - var provider = ScmCodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( - declaringType.Namespace, - declaringType.ClrMetadataName, - null, - includeReferencedAssemblies: true); - - if (provider is null || !MethodSignatureHelper.IsPublicApi(provider)) - { - return false; - } - } - - return true; - } - private static CSharpType? GetBuildableAttributeTargetType(AttributeStatement attribute) { foreach (var argument in attribute.Arguments) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs index 82d1314192c..bbc1694e369 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs @@ -99,10 +99,9 @@ await MockHelpers.LoadMockGeneratorAsync( compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); var contextDefinition = new ModelReaderWriterContextDefinition(); - var buildableAttributes = GetBuildableAttributes(contextDefinition); + var file = new TypeProviderWriter(contextDefinition).Write(); - Assert.AreEqual(1, buildableAttributes.Count); - Assert.AreEqual("typeof(global::Sample.Models.PublicModel)", buildableAttributes[0].Arguments[0].ToDisplayString()); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } [TestCase(TypeSignatureModifiers.Public, 2)] @@ -143,30 +142,6 @@ public void NonPublicFrameworkTypesDoNotContributeBuildableAttributes() Assert.AreEqual("typeof(global::Sample.Models.PublicModel)", buildableAttributes[0].Arguments[0].ToDisplayString()); } - [Test] - public async Task NonPublicTypesAreNotRestoredFromLastContract() - { - await MockHelpers.LoadMockGeneratorAsync( - inputModels: () => - [ - InputFactory.Model("InternalModel", access: "internal"), - InputFactory.Model("PublicModel") - ], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - var contextDefinition = new ModelReaderWriterContextDefinition(); - var buildableAttributes = GetBuildableAttributes(contextDefinition); - - CollectionAssert.AreEquivalent( - new[] - { - "typeof(global::Sample.Models.PublicModel)", - "typeof(global::Sample.Models.PublicContainer.NestedModel)" - }, - buildableAttributes.Select(a => a.Arguments[0].ToDisplayString())); - } - [TestCase(true)] [TestCase(false)] public void ValidateModelReaderWriterBuildableAttributesAreGeneratedForNonModelsThatImplementMRW(bool implementsIPersistable) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectCustomizedAccessibility.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectCustomizedAccessibility.cs new file mode 100644 index 00000000000..b9b6bb15d08 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectCustomizedAccessibility.cs @@ -0,0 +1,14 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Sample.Models; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.PublicModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs deleted file mode 100644 index dda1188439f..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Sample.Models -{ - internal class InternalCustomModel - { - } - - internal class InternalContainer - { - public class NestedModel - { - } - } - - public class PublicContainer - { - public class NestedModel - { - } - } -} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs deleted file mode 100644 index 5a2862beb0b..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.ClientModel.Primitives; - -namespace Sample -{ - [ModelReaderWriterBuildable(typeof(Models.InternalModel))] - [ModelReaderWriterBuildable(typeof(Models.PublicModel))] - [ModelReaderWriterBuildable(typeof(Models.InternalCustomModel))] - [ModelReaderWriterBuildable(typeof(Models.InternalContainer.NestedModel))] - [ModelReaderWriterBuildable(typeof(Models.PublicContainer.NestedModel))] - public partial class SampleContext : ModelReaderWriterContext - { - } -} - -namespace Sample.Models -{ - public class InternalModel - { - } - - public class PublicModel - { - } - - public class InternalCustomModel - { - } - - public class InternalContainer - { - public class NestedModel - { - } - } - - public class PublicContainer - { - public class NestedModel - { - } - } -} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs index 4b0a81f2d4f..5ea3e2058a2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs @@ -19,19 +19,6 @@ internal static bool IsPublicApi(MethodSignatureModifiers modifiers) => (modifiers.HasFlag(MethodSignatureModifiers.Public) || modifiers.HasFlag(MethodSignatureModifiers.Protected)) && !modifiers.HasFlag(MethodSignatureModifiers.Private); - internal static bool IsPublicApi(TypeProvider typeProvider) - { - for (var provider = typeProvider; provider != null; provider = provider.DeclaringTypeProvider) - { - if (!provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) - { - return false; - } - } - - return true; - } - internal static bool ContainsSameParameters(MethodSignature method1, MethodSignature method2) { var count = method1.Parameters.Count; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs index d5ca40328bb..fb999456636 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs @@ -9,8 +9,6 @@ using Microsoft.TypeSpec.Generator.Providers; using Microsoft.TypeSpec.Generator.Statements; using Microsoft.TypeSpec.Generator.Tests.Common; -using Moq; -using Moq.Protected; using NUnit.Framework; using static Microsoft.TypeSpec.Generator.Snippets.Snippet; @@ -24,40 +22,6 @@ public void Setup() MockHelpers.LoadMockGenerator(); } - [TestCase(TypeSignatureModifiers.Public, true)] - [TestCase(TypeSignatureModifiers.Internal, false)] - [TestCase(TypeSignatureModifiers.Private, false)] - [TestCase(TypeSignatureModifiers.Protected, false)] - [TestCase(TypeSignatureModifiers.Protected | TypeSignatureModifiers.Internal, false)] - [TestCase(TypeSignatureModifiers.Private | TypeSignatureModifiers.Protected, false)] - public void IsPublicApiChecksTypeAccessibility(TypeSignatureModifiers accessibility, bool expected) - { - var provider = new Mock(); - provider.Object.Update(modifiers: accessibility | TypeSignatureModifiers.Class); - - Assert.AreEqual(expected, MethodSignatureHelper.IsPublicApi(provider.Object)); - } - - [TestCase(TypeSignatureModifiers.Public, true)] - [TestCase(TypeSignatureModifiers.Internal, false)] - [TestCase(TypeSignatureModifiers.Private, false)] - [TestCase(TypeSignatureModifiers.Protected, false)] - [TestCase(TypeSignatureModifiers.Protected | TypeSignatureModifiers.Internal, false)] - [TestCase(TypeSignatureModifiers.Private | TypeSignatureModifiers.Protected, false)] - public void IsPublicApiChecksAllDeclaringTypes(TypeSignatureModifiers accessibility, bool expected) - { - var outer = new Mock(); - outer.Object.Update(modifiers: accessibility | TypeSignatureModifiers.Class); - var middle = new Mock(); - middle.Object.Update(modifiers: TypeSignatureModifiers.Public | TypeSignatureModifiers.Class); - middle.Protected().Setup("BuildDeclaringTypeProvider").Returns(outer.Object); - var inner = new Mock(); - inner.Object.Update(modifiers: TypeSignatureModifiers.Public | TypeSignatureModifiers.Class); - inner.Protected().Setup("BuildDeclaringTypeProvider").Returns(middle.Object); - - Assert.AreEqual(expected, MethodSignatureHelper.IsPublicApi(inner.Object)); - } - [Test] public void ContainsSameParameters_SameParametersInSameOrder_ReturnsTrue() { From 11b7a5b6ab3153e9f658430d22c06e045612de21 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:00:54 +0000 Subject: [PATCH 06/10] fix(http-client-csharp): preserve nested visibility filtering Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ModelReaderWriterContextDefinition.cs | 39 ++++++++++++++-- ...ModelReaderWriterContextDefinitionTests.cs | 24 ++++++++++ .../Models.cs | 31 +++++++++++++ .../SampleContext.cs | 46 +++++++++++++++++++ 4 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs index 500b917e108..0a1120737c0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs @@ -68,7 +68,17 @@ protected override IReadOnlyList BuildAttributes() } foreach (var provider in buildableProviders) { - if (!provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) + var isPublicProvider = true; + for (var providerToCheck = provider; providerToCheck != null; providerToCheck = providerToCheck.DeclaringTypeProvider) + { + if (!providerToCheck.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) + { + isPublicProvider = false; + break; + } + } + + if (!isPublicProvider || !ShouldWriteProvider(provider) || customizedBuildableTypes.Contains(GetTypeIdentity(provider.Type))) { @@ -172,12 +182,34 @@ private void AddLastContractBuildableAttributes( continue; } + var hasPublicDeclaringTypes = true; + for (var declaringType = targetType.DeclaringType; declaringType != null; declaringType = declaringType.DeclaringType) + { + var declaringProvider = ScmCodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( + declaringType.Namespace, + declaringType.ClrMetadataName, + null, + includeReferencedAssemblies: true); + + if (declaringProvider is null || !declaringProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) + { + hasPublicDeclaringTypes = false; + break; + } + } + + if (!hasPublicDeclaringTypes) + { + continue; + } + var identity = GetTypeIdentity(targetType); TypeProvider? resolvedProvider; if (outputLibraryProviders.TryGetValue(identity, out var outputLibraryProvider)) { - if (!ShouldWriteProvider(outputLibraryProvider)) + if (!outputLibraryProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) + || !ShouldWriteProvider(outputLibraryProvider)) { continue; } @@ -191,7 +223,8 @@ private void AddLastContractBuildableAttributes( null, includeReferencedAssemblies: true); - if (resolvedProvider is null) + if (resolvedProvider is null + || !resolvedProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) { continue; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs index bbc1694e369..5a2616577dc 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs @@ -142,6 +142,30 @@ public void NonPublicFrameworkTypesDoNotContributeBuildableAttributes() Assert.AreEqual("typeof(global::Sample.Models.PublicModel)", buildableAttributes[0].Arguments[0].ToDisplayString()); } + [Test] + public async Task NonPublicTypesAreNotRestoredFromLastContract() + { + await MockHelpers.LoadMockGeneratorAsync( + inputModels: () => + [ + InputFactory.Model("InternalModel", access: "internal"), + InputFactory.Model("PublicModel") + ], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var contextDefinition = new ModelReaderWriterContextDefinition(); + var buildableAttributes = GetBuildableAttributes(contextDefinition); + + CollectionAssert.AreEquivalent( + new[] + { + "typeof(global::Sample.Models.PublicModel)", + "typeof(global::Sample.Models.PublicContainer.NestedModel)" + }, + buildableAttributes.Select(a => a.Arguments[0].ToDisplayString())); + } + [TestCase(true)] [TestCase(false)] public void ValidateModelReaderWriterBuildableAttributesAreGeneratedForNonModelsThatImplementMRW(bool implementsIPersistable) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs new file mode 100644 index 00000000000..062ff11f52c --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Sample.Models +{ + internal partial class InternalModel + { + } + + public partial class PublicModel + { + } + + internal partial class InternalCustomModel + { + } + + internal partial class InternalContainer + { + public partial class NestedModel + { + } + } + + public partial class PublicContainer + { + public partial class NestedModel + { + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs new file mode 100644 index 00000000000..3ad46f5ba37 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.ClientModel.Primitives; + +namespace Sample +{ + [ModelReaderWriterBuildable(typeof(Models.InternalModel))] + [ModelReaderWriterBuildable(typeof(Models.PublicModel))] + [ModelReaderWriterBuildable(typeof(Models.InternalCustomModel))] + [ModelReaderWriterBuildable(typeof(Models.InternalContainer.NestedModel))] + [ModelReaderWriterBuildable(typeof(Models.PublicContainer.NestedModel))] + public partial class SampleContext : ModelReaderWriterContext + { + } +} + +namespace Sample.Models +{ + public class InternalModel + { + } + + public class PublicModel + { + } + + public class InternalCustomModel + { + } + + public class InternalContainer + { + public class NestedModel + { + } + } + + public class PublicContainer + { + public class NestedModel + { + } + } +} + From 086890f179ee982f2a15539657f53f426cb77ef8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:14:12 +0000 Subject: [PATCH 07/10] refactor(http-client-csharp): simplify provider visibility check and revert last-contract filtering Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../ModelReaderWriterContextDefinition.cs | 39 ++-------------- ...ModelReaderWriterContextDefinitionTests.cs | 24 ---------- .../Models.cs | 31 ------------- .../SampleContext.cs | 46 ------------------- 4 files changed, 3 insertions(+), 137 deletions(-) delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs index 0a1120737c0..500b917e108 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs @@ -68,17 +68,7 @@ protected override IReadOnlyList BuildAttributes() } foreach (var provider in buildableProviders) { - var isPublicProvider = true; - for (var providerToCheck = provider; providerToCheck != null; providerToCheck = providerToCheck.DeclaringTypeProvider) - { - if (!providerToCheck.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) - { - isPublicProvider = false; - break; - } - } - - if (!isPublicProvider + if (!provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) || !ShouldWriteProvider(provider) || customizedBuildableTypes.Contains(GetTypeIdentity(provider.Type))) { @@ -182,34 +172,12 @@ private void AddLastContractBuildableAttributes( continue; } - var hasPublicDeclaringTypes = true; - for (var declaringType = targetType.DeclaringType; declaringType != null; declaringType = declaringType.DeclaringType) - { - var declaringProvider = ScmCodeModelGenerator.Instance.SourceInputModel.FindForTypeInCurrentCompilation( - declaringType.Namespace, - declaringType.ClrMetadataName, - null, - includeReferencedAssemblies: true); - - if (declaringProvider is null || !declaringProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) - { - hasPublicDeclaringTypes = false; - break; - } - } - - if (!hasPublicDeclaringTypes) - { - continue; - } - var identity = GetTypeIdentity(targetType); TypeProvider? resolvedProvider; if (outputLibraryProviders.TryGetValue(identity, out var outputLibraryProvider)) { - if (!outputLibraryProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) - || !ShouldWriteProvider(outputLibraryProvider)) + if (!ShouldWriteProvider(outputLibraryProvider)) { continue; } @@ -223,8 +191,7 @@ private void AddLastContractBuildableAttributes( null, includeReferencedAssemblies: true); - if (resolvedProvider is null - || !resolvedProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)) + if (resolvedProvider is null) { continue; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs index 5a2616577dc..bbc1694e369 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs @@ -142,30 +142,6 @@ public void NonPublicFrameworkTypesDoNotContributeBuildableAttributes() Assert.AreEqual("typeof(global::Sample.Models.PublicModel)", buildableAttributes[0].Arguments[0].ToDisplayString()); } - [Test] - public async Task NonPublicTypesAreNotRestoredFromLastContract() - { - await MockHelpers.LoadMockGeneratorAsync( - inputModels: () => - [ - InputFactory.Model("InternalModel", access: "internal"), - InputFactory.Model("PublicModel") - ], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); - - var contextDefinition = new ModelReaderWriterContextDefinition(); - var buildableAttributes = GetBuildableAttributes(contextDefinition); - - CollectionAssert.AreEquivalent( - new[] - { - "typeof(global::Sample.Models.PublicModel)", - "typeof(global::Sample.Models.PublicContainer.NestedModel)" - }, - buildableAttributes.Select(a => a.Arguments[0].ToDisplayString())); - } - [TestCase(true)] [TestCase(false)] public void ValidateModelReaderWriterBuildableAttributesAreGeneratedForNonModelsThatImplementMRW(bool implementsIPersistable) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs deleted file mode 100644 index 062ff11f52c..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract(Custom)/Models.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Sample.Models -{ - internal partial class InternalModel - { - } - - public partial class PublicModel - { - } - - internal partial class InternalCustomModel - { - } - - internal partial class InternalContainer - { - public partial class NestedModel - { - } - } - - public partial class PublicContainer - { - public partial class NestedModel - { - } - } -} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs deleted file mode 100644 index 3ad46f5ba37..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicTypesAreNotRestoredFromLastContract/SampleContext.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.ClientModel.Primitives; - -namespace Sample -{ - [ModelReaderWriterBuildable(typeof(Models.InternalModel))] - [ModelReaderWriterBuildable(typeof(Models.PublicModel))] - [ModelReaderWriterBuildable(typeof(Models.InternalCustomModel))] - [ModelReaderWriterBuildable(typeof(Models.InternalContainer.NestedModel))] - [ModelReaderWriterBuildable(typeof(Models.PublicContainer.NestedModel))] - public partial class SampleContext : ModelReaderWriterContext - { - } -} - -namespace Sample.Models -{ - public class InternalModel - { - } - - public class PublicModel - { - } - - public class InternalCustomModel - { - } - - public class InternalContainer - { - public class NestedModel - { - } - } - - public class PublicContainer - { - public class NestedModel - { - } - } -} - From 9ef6bef584e60fd4d3d0df8041adfe82b8199d7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:45:28 +0000 Subject: [PATCH 08/10] test(http-client-csharp): validate accessibility tests against TestData baselines Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- ...ModelReaderWriterContextDefinitionTests.cs | 26 ++++++++----------- ...esRespectUpdatedAccessibility(Internal).cs | 14 ++++++++++ ...tesRespectUpdatedAccessibility(Private).cs | 14 ++++++++++ ...tUpdatedAccessibility(PrivateProtected).cs | 14 ++++++++++ ...sRespectUpdatedAccessibility(Protected).cs | 14 ++++++++++ ...UpdatedAccessibility(ProtectedInternal).cs | 14 ++++++++++ ...utesRespectUpdatedAccessibility(Public).cs | 15 +++++++++++ ...TypesDoNotContributeBuildableAttributes.cs | 14 ++++++++++ 8 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Internal).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Private).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(PrivateProtected).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Protected).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(ProtectedInternal).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Public).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicFrameworkTypesDoNotContributeBuildableAttributes.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs index bbc1694e369..8f47a20c273 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs @@ -104,13 +104,13 @@ await MockHelpers.LoadMockGeneratorAsync( Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } - [TestCase(TypeSignatureModifiers.Public, 2)] - [TestCase(TypeSignatureModifiers.Internal, 1)] - [TestCase(TypeSignatureModifiers.Private, 1)] - [TestCase(TypeSignatureModifiers.Protected, 1)] - [TestCase(TypeSignatureModifiers.Protected | TypeSignatureModifiers.Internal, 1)] - [TestCase(TypeSignatureModifiers.Private | TypeSignatureModifiers.Protected, 1)] - public void BuildableAttributesRespectUpdatedAccessibility(TypeSignatureModifiers accessibility, int expectedCount) + [TestCase(TypeSignatureModifiers.Public, "Public")] + [TestCase(TypeSignatureModifiers.Internal, "Internal")] + [TestCase(TypeSignatureModifiers.Private, "Private")] + [TestCase(TypeSignatureModifiers.Protected, "Protected")] + [TestCase(TypeSignatureModifiers.Protected | TypeSignatureModifiers.Internal, "ProtectedInternal")] + [TestCase(TypeSignatureModifiers.Private | TypeSignatureModifiers.Protected, "PrivateProtected")] + public void BuildableAttributesRespectUpdatedAccessibility(TypeSignatureModifiers accessibility, string expectedFile) { var provider = new TestMrwSerialization(implementsPersistableModel: true, includeDepModelProperty: true); MockHelpers.LoadMockGenerator(createOutputLibrary: () => new TestOutputLibrary([provider])); @@ -119,12 +119,9 @@ public void BuildableAttributesRespectUpdatedAccessibility(TypeSignatureModifier provider.Update(modifiers: accessibility | TypeSignatureModifiers.Class); var contextDefinition = new ModelReaderWriterContextDefinition(); - var buildableAttributes = GetBuildableAttributes(contextDefinition); + var file = new TypeProviderWriter(contextDefinition).Write(); - Assert.AreEqual(expectedCount, buildableAttributes.Count); - Assert.AreEqual( - "typeof(global::Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions.ModelReaderWriterContextDefinitionTests.DependencyModel)", - buildableAttributes[0].Arguments[0].ToDisplayString()); + Assert.AreEqual(Helpers.GetExpectedFromFile(expectedFile), file.Content); } [Test] @@ -136,10 +133,9 @@ public void NonPublicFrameworkTypesDoNotContributeBuildableAttributes() createCSharpTypeCoreFallback: input => input == InputPrimitiveType.String); var contextDefinition = new ModelReaderWriterContextDefinition(); - var buildableAttributes = GetBuildableAttributes(contextDefinition); + var file = new TypeProviderWriter(contextDefinition).Write(); - Assert.AreEqual(1, buildableAttributes.Count); - Assert.AreEqual("typeof(global::Sample.Models.PublicModel)", buildableAttributes[0].Arguments[0].ToDisplayString()); + Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } [TestCase(true)] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Internal).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Internal).cs new file mode 100644 index 00000000000..18610da3851 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Internal).cs @@ -0,0 +1,14 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions.ModelReaderWriterContextDefinitionTests.DependencyModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Private).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Private).cs new file mode 100644 index 00000000000..18610da3851 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Private).cs @@ -0,0 +1,14 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions.ModelReaderWriterContextDefinitionTests.DependencyModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(PrivateProtected).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(PrivateProtected).cs new file mode 100644 index 00000000000..18610da3851 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(PrivateProtected).cs @@ -0,0 +1,14 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions.ModelReaderWriterContextDefinitionTests.DependencyModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Protected).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Protected).cs new file mode 100644 index 00000000000..18610da3851 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Protected).cs @@ -0,0 +1,14 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions.ModelReaderWriterContextDefinitionTests.DependencyModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(ProtectedInternal).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(ProtectedInternal).cs new file mode 100644 index 00000000000..18610da3851 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(ProtectedInternal).cs @@ -0,0 +1,14 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions.ModelReaderWriterContextDefinitionTests.DependencyModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Public).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Public).cs new file mode 100644 index 00000000000..b79960b1301 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Public).cs @@ -0,0 +1,15 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions.ModelReaderWriterContextDefinitionTests.DependencyModel))] + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.TestMrwSerialization))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicFrameworkTypesDoNotContributeBuildableAttributes.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicFrameworkTypesDoNotContributeBuildableAttributes.cs new file mode 100644 index 00000000000..b9b6bb15d08 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/NonPublicFrameworkTypesDoNotContributeBuildableAttributes.cs @@ -0,0 +1,14 @@ +// + +#nullable disable + +using System.ClientModel.Primitives; +using Sample.Models; + +namespace Sample +{ + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.Models.PublicModel))] + public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext + { + } +} From 50dd958dda1999fc9cefee834792d59dfe270da1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:54:57 +0000 Subject: [PATCH 09/10] fix(http-client-csharp): treat protected types as public API in context attributes Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelReaderWriterContextDefinition.cs | 7 ++++++- ...ableAttributesRespectUpdatedAccessibility(Protected).cs | 1 + ...ibutesRespectUpdatedAccessibility(ProtectedInternal).cs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs index 500b917e108..0f2718d4fa3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs @@ -68,7 +68,12 @@ protected override IReadOnlyList BuildAttributes() } foreach (var provider in buildableProviders) { - if (!provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) + // protected internal is part of the public API surface, but private protected is not + var isPublicApi = provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) + || (provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Protected) + && !provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Private)); + + if (!isPublicApi || !ShouldWriteProvider(provider) || customizedBuildableTypes.Contains(GetTypeIdentity(provider.Type))) { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Protected).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Protected).cs index 18610da3851..b79960b1301 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Protected).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(Protected).cs @@ -8,6 +8,7 @@ namespace Sample { [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions.ModelReaderWriterContextDefinitionTests.DependencyModel))] + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.TestMrwSerialization))] public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext { } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(ProtectedInternal).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(ProtectedInternal).cs index 18610da3851..b79960b1301 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(ProtectedInternal).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildableAttributesRespectUpdatedAccessibility(ProtectedInternal).cs @@ -8,6 +8,7 @@ namespace Sample { [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.Definitions.ModelReaderWriterContextDefinitionTests.DependencyModel))] + [global::System.ClientModel.Primitives.ModelReaderWriterBuildableAttribute(typeof(global::Sample.TestMrwSerialization))] public partial class SampleContext : global::System.ClientModel.Primitives.ModelReaderWriterContext { } From 9c346f071c2f11ab6e310f2842ff499b58277750 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:59:09 +0000 Subject: [PATCH 10/10] refactor(http-client-csharp): extract IsPublicApi private helper Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../Providers/ModelReaderWriterContextDefinition.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs index 0f2718d4fa3..dfec556761d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs @@ -68,12 +68,7 @@ protected override IReadOnlyList BuildAttributes() } foreach (var provider in buildableProviders) { - // protected internal is part of the public API surface, but private protected is not - var isPublicApi = provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) - || (provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Protected) - && !provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Private)); - - if (!isPublicApi + if (!IsPublicApi(provider) || !ShouldWriteProvider(provider) || customizedBuildableTypes.Contains(GetTypeIdentity(provider.Type))) { @@ -94,6 +89,12 @@ protected override IReadOnlyList BuildAttributes() return attributes.OrderBy(a => GetSimpleTypeName(a.Key)).Select(kvp => kvp.Value).ToList(); } + // protected internal is part of the public API surface, but private protected is not + private static bool IsPublicApi(TypeProvider provider) + => provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) + || (provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Protected) + && !provider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Private)); + protected override IReadOnlyList BuildAttributesForBackCompatibility(IReadOnlyList originalAttributes) { if (LastContractView?.Attributes is not { Count: > 0 })