diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/CSharpTypeExtensions.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/CSharpTypeExtensions.cs index b62dc790b68..f7a7f22db7e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/CSharpTypeExtensions.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/CSharpTypeExtensions.cs @@ -55,14 +55,9 @@ private static CSharpType EnsureNamespace(InputProperty? specProperty, CSharpTyp // Use the TypeFactory to get the correct namespace for the type which respects any customizations that have // been applied to the generated types. var newType = CodeModelGenerator.Instance.TypeFactory.CreateCSharpType(inputType); - if (specProperty?.IsRequired == false && newType?.IsCollection == false) - { - newType = newType.WithNullable(true); - } - if (newType != null) { - return newType; + return type.IsNullable ? newType.WithNullable(true) : newType; } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs index bcc011f46a8..d97e46be381 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelCustomizationTests.cs @@ -302,7 +302,7 @@ public async Task CanChangeListOfModelToReadOnlyListOfModel() var elementType = listProp.Type.ElementType; Assert.AreEqual("global::Sample.Models.Foo", elementType.ToString()); Assert.AreEqual("Sample.Models", elementType.Namespace); - Assert.IsTrue(elementType.IsNullable); + Assert.IsFalse(elementType.IsNullable); } [Test] @@ -336,9 +336,9 @@ public async Task CanChangeListOfEnumToReadOnlyListOfEnum() Assert.IsTrue(listProp.Type.IsList); var elementType = listProp.Type.ElementType; - Assert.AreEqual("global::Sample.Models.Foo?", elementType.ToString()); + Assert.AreEqual("global::Sample.Models.Foo", elementType.ToString()); Assert.AreEqual("Sample.Models", elementType.Namespace); - Assert.IsTrue(elementType.IsNullable); + Assert.IsFalse(elementType.IsNullable); Assert.IsFalse(elementType.IsStruct); Assert.IsFalse(elementType.IsLiteral); } @@ -414,7 +414,7 @@ public async Task CanChangeDictionaryOfModelToReadOnlyDictionaryOfModel() var elementType = listProp.Type.ElementType; Assert.AreEqual("global::Sample.Models.Foo", elementType.ToString()); Assert.AreEqual("Sample.Models", elementType.Namespace); - Assert.IsTrue(elementType.IsNullable); + Assert.IsFalse(elementType.IsNullable); } [Test] @@ -483,7 +483,7 @@ public async Task CanChangeModelPropertyWithChangedNamespace() var modelProp = modelTypeProvider.CanonicalView.Properties[0]; Assert.AreEqual("Prop1", modelProp.Name); - Assert.IsTrue(modelProp.Type.IsNullable); + Assert.IsFalse(modelProp.Type.IsNullable); Assert.IsFalse(modelProp.Body.HasSetter); Assert.AreEqual("global::Updated.Namespace.Models.Foo", modelProp.Type.ToString()); Assert.AreEqual("Updated.Namespace.Models", modelProp.Type.Namespace); @@ -626,6 +626,76 @@ public async Task CanChangeEnumToExtensibleEnum() Assert.IsTrue(enumProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public | TypeSignatureModifiers.Partial | TypeSignatureModifiers.Struct | TypeSignatureModifiers.ReadOnly)); } + [Test] + public async Task CanChangeEnumNullableTypeModelProperty() + { + var inputEnum = InputFactory.Int32Enum( + "Foo", + [("val1", 1), ("val2", 2), ("val3", 3)], + isExtensible: false + ); + var props = new[] + { + InputFactory.Property("Prop1", inputEnum), + }; + + var inputModel = InputFactory.Model("mockInputModel", properties: props); + + var mockGenerator = await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [inputModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelTypeProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel"); + AssertCommon(modelTypeProvider, "Sample.Models", "MockInputModel"); + + // the property should be added to the custom code view + Assert.AreEqual(1, modelTypeProvider.CustomCodeView!.Properties.Count); + // the canonical type should be changed + Assert.AreEqual(1, modelTypeProvider.CanonicalView!.Properties.Count); + + var modelProp = modelTypeProvider.CanonicalView.Properties[0]; + Assert.AreEqual("Prop1", modelProp.Name); + Assert.IsFalse(modelProp.Type.IsNullable); + Assert.IsFalse(modelProp.Body.HasSetter); + Assert.AreEqual("global::Sample.Models.Foo", modelProp.Type.ToString()); + Assert.AreEqual("Sample.Models", modelProp.Type.Namespace); + } + + [Test] + public async Task CanChangeEnumTypeModelPropertyToNullable() + { + var inputEnum = InputFactory.Int32Enum( + "Foo", + [("val1", 1), ("val2", 2), ("val3", 3)], + isExtensible: false + ); + var props = new[] + { + InputFactory.Property("Prop1", inputEnum), + }; + + var inputModel = InputFactory.Model("mockInputModel", properties: props); + + var mockGenerator = await MockHelpers.LoadMockGeneratorAsync( + inputModelTypes: [inputModel], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync()); + + var modelTypeProvider = mockGenerator.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel"); + AssertCommon(modelTypeProvider, "Sample.Models", "MockInputModel"); + + // the property should be added to the custom code view + Assert.AreEqual(1, modelTypeProvider.CustomCodeView!.Properties.Count); + // the canonical type should be changed + Assert.AreEqual(1, modelTypeProvider.CanonicalView!.Properties.Count); + + var modelProp = modelTypeProvider.CanonicalView.Properties[0]; + Assert.AreEqual("Prop1", modelProp.Name); + Assert.IsTrue(modelProp.Type.IsNullable); + Assert.IsFalse(modelProp.Body.HasSetter); + Assert.AreEqual("global::Sample.Models.Foo?", modelProp.Type.ToString()); + Assert.AreEqual("Sample.Models", modelProp.Type.Namespace); + } + [Test] public async Task CanChangeExtensibleEnumToEnum() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeDictionaryOfModelToReadOnlyDictionaryOfModel/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeDictionaryOfModelToReadOnlyDictionaryOfModel/MockInputModel.cs index 20abb7c90d7..f363ae062a4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeDictionaryOfModelToReadOnlyDictionaryOfModel/MockInputModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeDictionaryOfModelToReadOnlyDictionaryOfModel/MockInputModel.cs @@ -6,5 +6,5 @@ namespace Sample.Models; public partial class MockInputModel { - public readonly IReadOnlyDictionary Prop1 { get; }; + public readonly IReadOnlyDictionary Prop1 { get; } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeEnumNullableTypeModelProperty/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeEnumNullableTypeModelProperty/MockInputModel.cs new file mode 100644 index 00000000000..561e17ffc7a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeEnumNullableTypeModelProperty/MockInputModel.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using SampleTypeSpec; + +namespace Sample.Models; + +public partial class MockInputModel +{ + public Foo Prop1 { get; }; +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeEnumTypeModelPropertyToNullable/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeEnumTypeModelPropertyToNullable/MockInputModel.cs new file mode 100644 index 00000000000..a9d892ec6ec --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeEnumTypeModelPropertyToNullable/MockInputModel.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using SampleTypeSpec; + +namespace Sample.Models; + +public partial class MockInputModel +{ + public Foo? Prop1 { get; }; +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeListOfModelToReadOnlyListOfModel/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeListOfModelToReadOnlyListOfModel/MockInputModel.cs index b9defff36b1..e5e7aeb1c3d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeListOfModelToReadOnlyListOfModel/MockInputModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeListOfModelToReadOnlyListOfModel/MockInputModel.cs @@ -6,5 +6,5 @@ namespace Sample.Models; public partial class MockInputModel { - public readonly IReadOnlyList Prop1 { get; }; + public readonly IReadOnlyList Prop1 { get; } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelPropertyWithChangedNamespace/MockInputModel.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelPropertyWithChangedNamespace/MockInputModel.cs index 561e17ffc7a..288f3532f69 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelPropertyWithChangedNamespace/MockInputModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelCustomizationTests/CanChangeModelPropertyWithChangedNamespace/MockInputModel.cs @@ -6,5 +6,5 @@ namespace Sample.Models; public partial class MockInputModel { - public Foo Prop1 { get; }; + public Foo Prop1 { get; } }