From effed87055ba867b11eedbe32e613cdeeee5054b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:12:33 +0000 Subject: [PATCH 1/5] Initial plan From 3752159380aa88a9ba255849b06fdeee7f40621e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:29:50 +0000 Subject: [PATCH 2/5] fix(csharp): isolate public parameter variables from serialization Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../SerializationCustomizationTests.cs | 75 +++++++++++++++++++ ...iesKeepSerializationParameters(Context).cs | 10 +++ .../MockInputModel.cs | 20 +++++ .../MockInputModel.cs | 42 +++++++++++ ...edPropertiesKeepSerializationParameters.cs | 44 +++++++++++ .../src/Providers/ParameterProvider.cs | 1 - ...ExactParameterIsRestoredWithExactRename.cs | 4 +- ...otationRestoredBySignatureMatch(Casing).cs | 4 +- ...erChainedRenameRestoredBySignatureMatch.cs | 6 +- ...tructorParameterPreservesDateTimeSuffix.cs | 4 +- ...ParameterRenameRestoredBySignatureMatch.cs | 4 +- ...ExactParameterIsRestoredWithExactRename.cs | 4 +- .../test/Providers/ParameterProviderTests.cs | 17 +++++ 13 files changed, 221 insertions(+), 14 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Context).cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Custom)/MockInputModel.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Previous)/MockInputModel.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs index 9cc71646129..a14093a82b6 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs @@ -1,13 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Reflection; +using System.Text.Json; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; using Microsoft.TypeSpec.Generator.Providers; +using Microsoft.TypeSpec.Generator.Snippets; using Microsoft.TypeSpec.Generator.Tests.Common; using NUnit.Framework; @@ -15,6 +21,75 @@ namespace Microsoft.TypeSpec.Generator.ClientModel.Tests.Providers.MrwSerializat { public class SerializationCustomizationTests { + [Test] + public async Task BackCompat_RenamedPropertiesKeepSerializationParameters( + [Values(false, true)] bool useLastContract) + { + var inputEnum = InputFactory.StringEnum("DestinationKind", [("VirtualNetwork", "virtualNetwork")]); + var inputModel = InputFactory.Model("MockInputModel", properties: [ + InputFactory.Property("isEnabled", InputPrimitiveType.Boolean, isRequired: true, serializedName: "enabled"), + InputFactory.Property("destinationType", inputEnum, isRequired: true, serializedName: "type") + ], + usage: InputModelTypeUsage.Input | InputModelTypeUsage.Output | InputModelTypeUsage.Json); + var mockGenerator = await MockHelpers.LoadMockGeneratorAsync( + inputEnums: () => [inputEnum], + inputModels: () => [inputModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync("Custom"), + lastContractCompilation: useLastContract + ? async () => await Helpers.GetCompilationFromDirectoryAsync("Previous") + : null); + + var modelProvider = mockGenerator.Object.OutputLibrary.TypeProviders.OfType().Single(); + var serializationProvider = modelProvider.SerializationProviders.Single(t => t is MrwSerializationTypeDefinition); + modelProvider.EnsureBuilt(); + serializationProvider.EnsureBuilt(); + modelProvider.ProcessTypeForBackCompatibility(); + + var publicConstructor = modelProvider.Constructors.Single(c => + c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)); + Assert.That(publicConstructor.Signature.Parameters.Select(p => p.Name), + Is.EqualTo(useLastContract ? new[] { "enabled", "type" } : new[] { "isEnabled", "destinationType" })); + Assert.That(publicConstructor.Signature.Parameters.Select(p => p.Property?.Name), + Is.EqualTo(new[] { "IsEnabled", "DestinationType" })); + Assert.That(modelProvider.FullConstructor.Signature.Parameters.Take(2).Select(p => p.Name), + Is.EqualTo(new[] { "isEnabled", "destinationType" })); + Assert.That(modelProvider.FullConstructor.Signature.Parameters.Take(2).Select(p => p.AsVariable().Declaration.RequestedName), + Is.EqualTo(new[] { "isEnabled", "destinationType" })); + + var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( + serializationProvider, name => name == "DeserializeMockInputModel")); + Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location))) + { + mockGenerator.Object.AddMetadataReference(MetadataReference.CreateFromFile(assembly.Location)); + } + var providers = mockGenerator.Object.OutputLibrary.TypeProviders + .Where(p => p is not CustomCodeAttributeDefinition and not Utf8JsonBinaryContentDefinition and not BinaryContentHelperDefinition); + var sources = providers.Concat(providers.SelectMany(p => p.SerializationProviders)) + .Select(p => new TypeProviderWriter(p).Write()) + .Select(f => (f.Name, f.Content)) + .Append(("SampleContext.cs", Helpers.GetExpectedFromFile("Context"))) + .Append(("CompatibilityAliases.cs", File.ReadAllText( + Path.Combine(Helpers.GetAssetFileOrDirectoryPath(false, "Custom"), "MockInputModel.cs")))); + var compilation = await Helpers.GetCompilationFromSourceFilesAsync(sources); + using var stream = new MemoryStream(); + var result = compilation.WithAssemblyName($"RenamedProperties{Guid.NewGuid():N}").Emit(stream); + Assert.IsTrue(result.Success, string.Join(Environment.NewLine, result.Diagnostics)); + + var modelType = Assembly.Load(stream.ToArray()).GetType("Sample.Models.MockInputModel")!; + var deserialize = modelType.GetMethod("DeserializeMockInputModel", BindingFlags.NonPublic | BindingFlags.Static)!; + foreach (var enabled in new[] { true, false }) + { + using var document = JsonDocument.Parse($"{{\"enabled\":{enabled.ToString().ToLowerInvariant()},\"type\":\"virtualNetwork\"}}"); + var model = deserialize.Invoke(null, [document.RootElement, new System.ClientModel.Primitives.ModelReaderWriterOptions("J")]); + Assert.AreEqual(enabled, modelType.GetProperty("IsEnabled")!.GetValue(model)); + Assert.AreEqual(enabled, modelType.GetProperty("Enabled")!.GetValue(model)); + Assert.AreEqual("VirtualNetwork", modelType.GetProperty("DestinationType")!.GetValue(model)!.ToString()); + Assert.AreEqual("VirtualNetwork", modelType.GetProperty("Type")!.GetValue(model)!.ToString()); + } + } + [Test] public async Task CanChangePropertyName() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Context).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Context).cs new file mode 100644 index 00000000000..0cd9ff47026 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Context).cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Sample +{ + public partial class SampleContext + { + public static SampleContext Default { get; } = new SampleContext(); + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Custom)/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Custom)/MockInputModel.cs new file mode 100644 index 00000000000..c59abf94ed2 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Custom)/MockInputModel.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Sample.Models +{ + public partial class MockInputModel + { + public bool Enabled + { + get => IsEnabled; + set => IsEnabled = value; + } + + public DestinationKind Type + { + get => DestinationType; + set => DestinationType = value; + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Previous)/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Previous)/MockInputModel.cs new file mode 100644 index 00000000000..18e44ccd966 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Previous)/MockInputModel.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; + +namespace Sample.Models +{ + public partial class MockInputModel + { + public MockInputModel(bool enabled, DestinationKind type) + { + IsEnabled = enabled; + DestinationType = type; + } + + internal MockInputModel(bool enabled, DestinationKind type, IDictionary additionalBinaryDataProperties) + { + IsEnabled = enabled; + DestinationType = type; + } + + public bool IsEnabled { get; set; } + public bool Enabled + { + get => IsEnabled; + set => IsEnabled = value; + } + + public DestinationKind DestinationType { get; set; } + public DestinationKind Type + { + get => DestinationType; + set => DestinationType = value; + } + } + + public enum DestinationKind + { + VirtualNetwork + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs new file mode 100644 index 00000000000..c2759256db8 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs @@ -0,0 +1,44 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text.Json; +using Sample.Models; + +namespace Sample +{ + public partial class MockInputModel + { + internal static global::Sample.Models.MockInputModel DeserializeMockInputModel(global::System.Text.Json.JsonElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + return null; + } + bool isEnabled = default; + global::Sample.Models.DestinationKind destinationType = default; + global::System.Collections.Generic.IDictionary additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary(); + foreach (var prop in element.EnumerateObject()) + { + if (prop.NameEquals("enabled"u8)) + { + isEnabled = prop.Value.GetBoolean(); + continue; + } + if (prop.NameEquals("type"u8)) + { + destinationType = prop.Value.GetString().ToDestinationKind(); + continue; + } + if ((options.Format != "W")) + { + additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); + } + } + return new global::Sample.Models.MockInputModel(isEnabled, destinationType, additionalBinaryDataProperties); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs index 08635be7915..9010e14bc6b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ParameterProvider.cs @@ -191,7 +191,6 @@ private ParameterProvider BuildInputVariant() wireInfo: WireInfo, validation: Validation) { - _asVariable = _asVariable, SpreadSource = SpreadSource, InputParameter = InputParameter, IsExactName = IsExactName diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorNonExactParameterIsRestoredWithExactRename.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorNonExactParameterIsRestoredWithExactRename.cs index a1dd4a34ae5..6d027752c51 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorNonExactParameterIsRestoredWithExactRename.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorNonExactParameterIsRestoredWithExactRename.cs @@ -20,10 +20,10 @@ public MockInputModel(string newName, int oldCount) NewCount = oldCount; } - internal MockInputModel(string newName, int oldCount, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) + internal MockInputModel(string newName, int newCount, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) { new_name = newName; - NewCount = oldCount; + NewCount = newCount; _additionalBinaryDataProperties = additionalBinaryDataProperties; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterCasingAndRotationRestoredBySignatureMatch(Casing).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterCasingAndRotationRestoredBySignatureMatch(Casing).cs index 2ce0ded32f7..14ded0c7832 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterCasingAndRotationRestoredBySignatureMatch(Casing).cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterCasingAndRotationRestoredBySignatureMatch(Casing).cs @@ -19,9 +19,9 @@ public CasingModel(string vmwareSiteId) VMwareSiteId = vmwareSiteId; } - internal CasingModel(string vmwareSiteId, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) + internal CasingModel(string vMwareSiteId, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) { - VMwareSiteId = vmwareSiteId; + VMwareSiteId = vMwareSiteId; _additionalBinaryDataProperties = additionalBinaryDataProperties; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterChainedRenameRestoredBySignatureMatch.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterChainedRenameRestoredBySignatureMatch.cs index 2722d0e2ba4..0c410e5e793 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterChainedRenameRestoredBySignatureMatch.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterChainedRenameRestoredBySignatureMatch.cs @@ -21,10 +21,10 @@ public MockInputModel(string name, string vmName) Name = vmName; } - internal MockInputModel(string name, string vmName, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) + internal MockInputModel(string skuName, string name, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) { - SkuName = name; - Name = vmName; + SkuName = skuName; + Name = name; _additionalBinaryDataProperties = additionalBinaryDataProperties; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix.cs index 95a351ea79f..826f1eb825f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterPreservesDateTimeSuffix.cs @@ -16,9 +16,9 @@ public DateTimeModel(global::System.DateTimeOffset startTime) StartsOn = startTime; } - internal DateTimeModel(global::System.DateTimeOffset startTime, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) + internal DateTimeModel(global::System.DateTimeOffset startsOn, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) { - StartsOn = startTime; + StartsOn = startsOn; _additionalBinaryDataProperties = additionalBinaryDataProperties; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterRenameRestoredBySignatureMatch.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterRenameRestoredBySignatureMatch.cs index 3fc7eee2f25..0ce6b8353cd 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterRenameRestoredBySignatureMatch.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorParameterRenameRestoredBySignatureMatch.cs @@ -21,10 +21,10 @@ public MockInputModel(string unchanged, string oldName) NewName = oldName; } - internal MockInputModel(string unchanged, string oldName, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) + internal MockInputModel(string unchanged, string newName, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) { Unchanged = unchanged; - NewName = oldName; + NewName = newName; _additionalBinaryDataProperties = additionalBinaryDataProperties; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorSameTypedNonExactParameterIsRestoredWithExactRename.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorSameTypedNonExactParameterIsRestoredWithExactRename.cs index 31dbe9fc984..7f1c2a0b6a8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorSameTypedNonExactParameterIsRestoredWithExactRename.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ConstructorSameTypedNonExactParameterIsRestoredWithExactRename.cs @@ -21,9 +21,9 @@ public MockInputModel(string oldUnchanged, string newName) new_name = newName; } - internal MockInputModel(string oldUnchanged, string newName, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) + internal MockInputModel(string unchanged, string newName, global::System.Collections.Generic.IDictionary additionalBinaryDataProperties) { - Unchanged = oldUnchanged; + Unchanged = unchanged; new_name = newName; _additionalBinaryDataProperties = additionalBinaryDataProperties; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs index 2a7f2b006c2..fa3f4cfee46 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ParameterProviderTests.cs @@ -205,6 +205,23 @@ public void ToPublicInputParameterCopiesProperties() Assert.AreEqual(parameter.Type.InputType, publicParameter.Type); } + [Test] + public void ToPublicInputParameterDoesNotShareVariable() + { + var parameter = new ParameterProvider("isEnabled", $"Description", typeof(bool)); + var variable = parameter.AsVariable(); + var publicParameter = parameter.ToPublicInputParameter(); + var publicVariable = publicParameter.AsVariable(); + + publicParameter.Update(name: "enabled"); + + Assert.AreEqual("isEnabled", parameter.Name); + Assert.AreEqual("isEnabled", variable.Declaration.RequestedName); + Assert.AreEqual("enabled", publicVariable.Declaration.RequestedName); + Assert.AreSame(variable, parameter.AsVariable()); + Assert.AreSame(publicVariable, publicParameter.AsVariable()); + } + [Test] public void WithRefCopiesProperties() { From 3f0169d07d079330c304697d40ac2fcda0faa011 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:37:41 +0000 Subject: [PATCH 3/5] test(csharp): cover optional enum in back-compat deserialization regression Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../SerializationCustomizationTests.cs | 6 ++++-- .../MockInputModel.cs | 4 +++- ...t_RenamedPropertiesKeepSerializationParameters.cs | 12 +++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs index a14093a82b6..358eb0525c4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs @@ -28,7 +28,8 @@ public async Task BackCompat_RenamedPropertiesKeepSerializationParameters( var inputEnum = InputFactory.StringEnum("DestinationKind", [("VirtualNetwork", "virtualNetwork")]); var inputModel = InputFactory.Model("MockInputModel", properties: [ InputFactory.Property("isEnabled", InputPrimitiveType.Boolean, isRequired: true, serializedName: "enabled"), - InputFactory.Property("destinationType", inputEnum, isRequired: true, serializedName: "type") + InputFactory.Property("destinationType", inputEnum, isRequired: true, serializedName: "type"), + InputFactory.Property("enforcement", inputEnum) ], usage: InputModelTypeUsage.Input | InputModelTypeUsage.Output | InputModelTypeUsage.Json); var mockGenerator = await MockHelpers.LoadMockGeneratorAsync( @@ -81,12 +82,13 @@ public async Task BackCompat_RenamedPropertiesKeepSerializationParameters( var deserialize = modelType.GetMethod("DeserializeMockInputModel", BindingFlags.NonPublic | BindingFlags.Static)!; foreach (var enabled in new[] { true, false }) { - using var document = JsonDocument.Parse($"{{\"enabled\":{enabled.ToString().ToLowerInvariant()},\"type\":\"virtualNetwork\"}}"); + using var document = JsonDocument.Parse($"{{\"enabled\":{enabled.ToString().ToLowerInvariant()},\"type\":\"virtualNetwork\",\"enforcement\":\"virtualNetwork\"}}"); var model = deserialize.Invoke(null, [document.RootElement, new System.ClientModel.Primitives.ModelReaderWriterOptions("J")]); Assert.AreEqual(enabled, modelType.GetProperty("IsEnabled")!.GetValue(model)); Assert.AreEqual(enabled, modelType.GetProperty("Enabled")!.GetValue(model)); Assert.AreEqual("VirtualNetwork", modelType.GetProperty("DestinationType")!.GetValue(model)!.ToString()); Assert.AreEqual("VirtualNetwork", modelType.GetProperty("Type")!.GetValue(model)!.ToString()); + Assert.AreEqual("VirtualNetwork", modelType.GetProperty("Enforcement")!.GetValue(model)!.ToString()); } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Previous)/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Previous)/MockInputModel.cs index 18e44ccd966..6ad9d6213bb 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Previous)/MockInputModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Previous)/MockInputModel.cs @@ -14,10 +14,11 @@ public MockInputModel(bool enabled, DestinationKind type) DestinationType = type; } - internal MockInputModel(bool enabled, DestinationKind type, IDictionary additionalBinaryDataProperties) + internal MockInputModel(bool enabled, DestinationKind type, DestinationKind? enforcement, IDictionary additionalBinaryDataProperties) { IsEnabled = enabled; DestinationType = type; + Enforcement = enforcement; } public bool IsEnabled { get; set; } @@ -28,6 +29,7 @@ public bool Enabled } public DestinationKind DestinationType { get; set; } + public DestinationKind? Enforcement { get; set; } public DestinationKind Type { get => DestinationType; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs index c2759256db8..39b616fba40 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs @@ -20,6 +20,7 @@ public partial class MockInputModel } bool isEnabled = default; global::Sample.Models.DestinationKind destinationType = default; + global::Sample.Models.DestinationKind? enforcement = default; global::System.Collections.Generic.IDictionary additionalBinaryDataProperties = new global::Sample.ChangeTrackingDictionary(); foreach (var prop in element.EnumerateObject()) { @@ -33,12 +34,21 @@ public partial class MockInputModel destinationType = prop.Value.GetString().ToDestinationKind(); continue; } + if (prop.NameEquals("enforcement"u8)) + { + if ((prop.Value.ValueKind == global::System.Text.Json.JsonValueKind.Null)) + { + continue; + } + enforcement = prop.Value.GetString().ToDestinationKind(); + continue; + } if ((options.Format != "W")) { additionalBinaryDataProperties.Add(prop.Name, prop.Value.GetUtf8Bytes()); } } - return new global::Sample.Models.MockInputModel(isEnabled, destinationType, additionalBinaryDataProperties); + return new global::Sample.Models.MockInputModel(isEnabled, destinationType, enforcement, additionalBinaryDataProperties); } } } From f2028e71e6074b358d3cc9e0e17805fbe4b3c585 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:50:15 +0000 Subject: [PATCH 4/5] test(csharp): use TestData for back-compat serialization validation Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../SerializationCustomizationTests.cs | 35 ------------------- ...iesKeepSerializationParameters(Context).cs | 10 ------ 2 files changed, 45 deletions(-) delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Context).cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs index 358eb0525c4..6ee0c22c596 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs @@ -1,14 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Reflection; -using System.Text.Json; using System.Threading.Tasks; -using Microsoft.CodeAnalysis; using Microsoft.TypeSpec.Generator.ClientModel.Providers; using Microsoft.TypeSpec.Generator.Input; using Microsoft.TypeSpec.Generator.Primitives; @@ -60,36 +55,6 @@ public async Task BackCompat_RenamedPropertiesKeepSerializationParameters( var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( serializationProvider, name => name == "DeserializeMockInputModel")); Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); - - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic && !string.IsNullOrEmpty(a.Location))) - { - mockGenerator.Object.AddMetadataReference(MetadataReference.CreateFromFile(assembly.Location)); - } - var providers = mockGenerator.Object.OutputLibrary.TypeProviders - .Where(p => p is not CustomCodeAttributeDefinition and not Utf8JsonBinaryContentDefinition and not BinaryContentHelperDefinition); - var sources = providers.Concat(providers.SelectMany(p => p.SerializationProviders)) - .Select(p => new TypeProviderWriter(p).Write()) - .Select(f => (f.Name, f.Content)) - .Append(("SampleContext.cs", Helpers.GetExpectedFromFile("Context"))) - .Append(("CompatibilityAliases.cs", File.ReadAllText( - Path.Combine(Helpers.GetAssetFileOrDirectoryPath(false, "Custom"), "MockInputModel.cs")))); - var compilation = await Helpers.GetCompilationFromSourceFilesAsync(sources); - using var stream = new MemoryStream(); - var result = compilation.WithAssemblyName($"RenamedProperties{Guid.NewGuid():N}").Emit(stream); - Assert.IsTrue(result.Success, string.Join(Environment.NewLine, result.Diagnostics)); - - var modelType = Assembly.Load(stream.ToArray()).GetType("Sample.Models.MockInputModel")!; - var deserialize = modelType.GetMethod("DeserializeMockInputModel", BindingFlags.NonPublic | BindingFlags.Static)!; - foreach (var enabled in new[] { true, false }) - { - using var document = JsonDocument.Parse($"{{\"enabled\":{enabled.ToString().ToLowerInvariant()},\"type\":\"virtualNetwork\",\"enforcement\":\"virtualNetwork\"}}"); - var model = deserialize.Invoke(null, [document.RootElement, new System.ClientModel.Primitives.ModelReaderWriterOptions("J")]); - Assert.AreEqual(enabled, modelType.GetProperty("IsEnabled")!.GetValue(model)); - Assert.AreEqual(enabled, modelType.GetProperty("Enabled")!.GetValue(model)); - Assert.AreEqual("VirtualNetwork", modelType.GetProperty("DestinationType")!.GetValue(model)!.ToString()); - Assert.AreEqual("VirtualNetwork", modelType.GetProperty("Type")!.GetValue(model)!.ToString()); - Assert.AreEqual("VirtualNetwork", modelType.GetProperty("Enforcement")!.GetValue(model)!.ToString()); - } } [Test] diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Context).cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Context).cs deleted file mode 100644 index 0cd9ff47026..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters(Context).cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -namespace Sample -{ - public partial class SampleContext - { - public static SampleContext Default { get; } = new SampleContext(); - } -} From d534ea08cba2fe0feb9331cc0ddb50e6dd552b7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 13 Oct 2026 17:03:26 +0000 Subject: [PATCH 5/5] test(csharp): include JSON writing in back-compat baseline Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../SerializationCustomizationTests.cs | 2 +- ...edPropertiesKeepSerializationParameters.cs | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs index 6ee0c22c596..afe3517713f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/SerializationCustomizationTests.cs @@ -53,7 +53,7 @@ public async Task BackCompat_RenamedPropertiesKeepSerializationParameters( Is.EqualTo(new[] { "isEnabled", "destinationType" })); var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( - serializationProvider, name => name == "DeserializeMockInputModel")); + serializationProvider, name => name is "DeserializeMockInputModel" or "JsonModelWriteCore")); Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs index 39b616fba40..52c467a1645 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/SerializationCustomizationTests/BackCompat_RenamedPropertiesKeepSerializationParameters.cs @@ -12,6 +12,39 @@ namespace Sample { public partial class MockInputModel { + protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.MockInputModel)} does not support writing '{format}' format."); + } + writer.WritePropertyName("enabled"u8); + writer.WriteBooleanValue(IsEnabled); + writer.WritePropertyName("type"u8); + writer.WriteStringValue(DestinationType.ToSerialString()); + if (global::Sample.Optional.IsDefined(Enforcement)) + { + writer.WritePropertyName("enforcement"u8); + writer.WriteStringValue(Enforcement.Value.ToSerialString()); + } + if (((options.Format != "W") && (_additionalBinaryDataProperties != null))) + { + foreach (var item in _additionalBinaryDataProperties) + { + writer.WritePropertyName(item.Key); +#if NET6_0_OR_GREATER + writer.WriteRawValue(item.Value); +#else + using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(item.Value)) + { + global::System.Text.Json.JsonSerializer.Serialize(writer, document.RootElement); + } +#endif + } + } + } + internal static global::Sample.Models.MockInputModel DeserializeMockInputModel(global::System.Text.Json.JsonElement element, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) { if ((element.ValueKind == global::System.Text.Json.JsonValueKind.Null))