Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional fixes for custom nullable types #4722

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ public async Task CanReplaceOpMethod()
}

// Validates that a method with a struct parameter can be replaced
[Test]
[TestCase(true)]
[TestCase(false)]
public async Task CanReplaceStructMethod(bool isStructCustomized)
Expand Down Expand Up @@ -190,7 +189,8 @@ public async Task CanReplaceStructMethod(bool isStructCustomized)
var customMethodParams = customMethods[0].Signature.Parameters;
Assert.AreEqual(1, customMethodParams.Count);
Assert.AreEqual("p1", customMethodParams[0].Name);
Assert.AreEqual(isStructCustomized ? "Sample.TestClient.MyStruct" : "MyStruct", customMethodParams[0].Type.Name);
Assert.AreEqual("MyStruct", customMethodParams[0].Type.Name);
Assert.AreEqual(isStructCustomized ? "Sample.TestClient" : string.Empty, customMethodParams[0].Type.Namespace);

Assert.IsTrue(customMethodParams[0].Type.IsStruct);
Assert.IsTrue(customMethodParams[0].Type.IsNullable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,5 @@ private static FileLinePositionSpan GetFileLinePosition(SyntaxReference? syntaxR

private IEnumerable<AttributeData> GetMemberSuppressionAttributes()
=> CustomCodeView?.GetAttributes()?.Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSuppressAttributeName) ?? [];
private IEnumerable<AttributeData> GetCodeGenSerializationAttributes()
=> CustomCodeView?.GetAttributes()?.Where(a => a.AttributeClass?.Name == CodeGenAttributes.CodeGenSerializationAttributeName) ?? [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.Generator.CSharp.Primitives;
Expand Down Expand Up @@ -154,7 +155,7 @@ public static string GetFullyQualifiedNameFromDisplayString(this ISymbol typeSym
{
// Special case for types that would not be defined in corlib, but should still be considered framework types.
"System.BinaryData" => typeof(BinaryData),
_ => System.Type.GetType(fullyQualifiedName)
_ => Type.GetType(fullyQualifiedName)
};
}

Expand All @@ -165,18 +166,28 @@ private static CSharpType ConstructCSharpTypeFromSymbol(
{
var typeArg = namedTypeSymbol?.TypeArguments.FirstOrDefault();
bool isValueType = typeSymbol.IsValueType;
bool isEnum = typeSymbol.TypeKind == TypeKind.Enum;
bool isNullable = fullyQualifiedName.StartsWith(NullableTypeName);
bool isEnum = typeSymbol.TypeKind == TypeKind.Enum || (isNullable && typeArg?.TypeKind == TypeKind.Enum);
bool isNullableUnknownType = isNullable && typeArg?.TypeKind == TypeKind.Error;
string name = isNullableUnknownType ? fullyQualifiedName : typeSymbol.Name;
string[] pieces = fullyQualifiedName.Split('.');
List<CSharpType> arguments = [];
INamedTypeSymbol? namedTypeArg = typeArg as INamedTypeSymbol;
INamedTypeSymbol? enumUnderlyingType = !isNullable ? namedTypeSymbol?.EnumUnderlyingType : namedTypeArg?.EnumUnderlyingType;

// For nullable types, we need to get the type arguments from the underlying type.
if (namedTypeSymbol?.IsGenericType == true &&
(!isNullable || (namedTypeArg?.IsGenericType == true)))
{
arguments.AddRange(namedTypeSymbol.TypeArguments.Select(GetCSharpType));
}

// handle nullables
if (isNullable)
if (isNullable && typeArg != null)
{
// System.Nullable`1[T] -> T
name = typeArg != null ? GetFullyQualifiedName(typeArg) : fullyQualifiedName;
pieces = name.Split('.');
name = typeArg.Name;
pieces = GetFullyQualifiedName(typeArg).Split('.');
}

return new CSharpType(
Expand All @@ -185,14 +196,14 @@ private static CSharpType ConstructCSharpTypeFromSymbol(
isValueType,
isNullable,
typeSymbol.ContainingType is not null ? GetCSharpType(typeSymbol.ContainingType) : null,
namedTypeSymbol is not null && !isNullableUnknownType ? [.. namedTypeSymbol.TypeArguments.Select(GetCSharpType)] : [],
arguments,
typeSymbol.DeclaredAccessibility == Accessibility.Public,
isValueType && !isEnum,
baseType: typeSymbol.BaseType is not null && typeSymbol.BaseType.TypeKind != TypeKind.Error && !isNullableUnknownType
? GetCSharpType(typeSymbol.BaseType)
: null,
underlyingEnumType: namedTypeSymbol is not null && namedTypeSymbol.EnumUnderlyingType is not null
? GetCSharpType(namedTypeSymbol.EnumUnderlyingType).FrameworkType
underlyingEnumType: enumUnderlyingType != null
? GetCSharpType(enumUnderlyingType).FrameworkType
: null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,30 @@ public async Task CanChangePropertyType()
Assert.AreEqual(new CSharpType(typeof(int[])), modelTypeProvider.CustomCodeView.Properties[0].Type);
}

[Test]
public async Task CanChangePropertyTypeToEnum()
{
var props = new[]
{
InputFactory.Property("Prop1", InputPrimitiveType.String)
};

var inputModel = InputFactory.Model("mockInputModel", properties: props);

var plugin = await MockHelpers.LoadMockPluginAsync(
inputModelTypes: [inputModel],
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

var modelTypeProvider = plugin.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 property type should be changed
Assert.AreEqual("SomeEnum", modelTypeProvider.CustomCodeView.Properties[0].Type.Name);
Assert.IsTrue(modelTypeProvider.CustomCodeView.Properties[0].Type.IsNullable);
}

[Test]
public async Task CanChangePropertyAccessibility()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#nullable disable

using Sample;
using Microsoft.Generator.CSharp.Customization;

namespace Sample.Models
{
public partial class MockInputModel
{
// CUSTOM: Changed type from string.
[CodeGenMember("Prop1")]
public SomeEnum? Prop1 { get; }
}

public enum SomeEnum
{
Foo,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public void ValidateProperties()
[TestCase(typeof(IList<string>))]
[TestCase(typeof(IList<string?>))]
[TestCase(typeof(IList<PropertyType>))]
[TestCase(typeof(IList<SomeEnum>))]
[TestCase(typeof(ReadOnlyMemory<byte>?))]
[TestCase(typeof(ReadOnlyMemory<byte>))]
[TestCase(typeof(ReadOnlyMemory<object>))]
Expand All @@ -80,7 +81,10 @@ public void ValidateProperties()
[TestCase(typeof(string[]))]
[TestCase(typeof(IDictionary<int, int>))]
[TestCase(typeof(BinaryData))]
public void ValidatePropertyTypes(Type propertyType)
[TestCase(typeof(SomeEnum), true)]
[TestCase(typeof(SomeEnum?), true)]
[TestCase(typeof(IDictionary<string, SomeEnum>))]
public void ValidatePropertyTypes(Type propertyType, bool isEnum = false)
{
// setup
var namedSymbol = new NamedSymbol(propertyType);
Expand All @@ -95,9 +99,15 @@ public void ValidatePropertyTypes(Type propertyType)
var property = _namedTypeSymbolProvider.Properties.FirstOrDefault();
Assert.IsNotNull(property);

bool isNullable = Nullable.GetUnderlyingType(propertyType) != null;
var expectedType = propertyType.FullName!.StartsWith("System") ? new CSharpType(propertyType, isNullable) :
new CSharpType(propertyType.Name, propertyType.Namespace!, false, isNullable, null, [], false, false);
Type? nullableUnderlyingType = Nullable.GetUnderlyingType(propertyType);
var propertyName = nullableUnderlyingType?.Name ?? propertyType.Name;
bool isNullable = nullableUnderlyingType != null;
bool isSystemType = propertyType.FullName!.StartsWith("System")
&& (!isNullable || nullableUnderlyingType?.Namespace?.StartsWith("System") == true);

var expectedType = isSystemType
? new CSharpType(propertyType, isNullable)
: new CSharpType(propertyName, propertyType.Namespace!, false, isNullable, null, [], false, false);

var propertyCSharpType = property!.Type;

Expand Down Expand Up @@ -188,5 +198,10 @@ public void ValidateFields()
Assert.AreEqual(expected.InitializationValue, actual.InitializationValue);
}
}

public enum SomeEnum
{
Foo,
}
}
}
Loading