Skip to content

Commit

Permalink
Handle nullable parameter types for method suppression (#4642)
Browse files Browse the repository at this point in the history
Fixes #4641
  • Loading branch information
JoshLove-msft authored Oct 9, 2024
1 parent 78238fb commit 9ab01ec
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 232 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ namespace Microsoft.Generator.CSharp.Providers
{
public sealed class NamedTypeSymbolProvider : TypeProvider
{
private const string GlobalPrefix = "global::";
private INamedTypeSymbol _namedTypeSymbol;

public NamedTypeSymbolProvider(INamedTypeSymbol namedTypeSymbol)
Expand All @@ -28,7 +27,7 @@ public NamedTypeSymbolProvider(INamedTypeSymbol namedTypeSymbol)

protected override string BuildName() => _namedTypeSymbol.Name;

protected override string GetNamespace() => GetFullyQualifiedNameFromDisplayString(_namedTypeSymbol.ContainingNamespace);
protected override string GetNamespace() => _namedTypeSymbol.ContainingNamespace.GetFullyQualifiedNameFromDisplayString();

public IEnumerable<AttributeData> GetAttributes() => _namedTypeSymbol.GetAttributes();

Expand Down Expand Up @@ -90,7 +89,7 @@ protected override FieldProvider[] BuildFields()

var fieldProvider = new FieldProvider(
modifiers,
GetCSharpType(fieldSymbol.Type),
fieldSymbol.Type.GetCSharpType(),
fieldSymbol.Name,
this,
GetSymbolXmlDoc(fieldSymbol, "summary"))
Expand All @@ -112,7 +111,7 @@ protected override PropertyProvider[] BuildProperties()
var propertyProvider = new PropertyProvider(
GetSymbolXmlDoc(propertySymbol, "summary"),
GetAccessModifier(propertySymbol.DeclaredAccessibility),
GetCSharpType(propertySymbol.Type),
propertySymbol.Type.GetCSharpType(),
propertySymbol.Name,
new AutoPropertyBody(propertySymbol.SetMethod is not null),
this)
Expand Down Expand Up @@ -179,7 +178,7 @@ private ParameterProvider ConvertToParameterProvider(IMethodSymbol methodSymbol,
return new ParameterProvider(
parameterSymbol.Name,
FormattableStringHelpers.FromString(GetParameterXmlDocumentation(methodSymbol, parameterSymbol)) ?? FormattableStringHelpers.Empty,
GetCSharpType(parameterSymbol.Type));
parameterSymbol.Type.GetCSharpType());
}

private void AddAdditionalModifiers(IMethodSymbol methodSymbol, ref MethodSignatureModifiers modifiers)
Expand Down Expand Up @@ -264,197 +263,12 @@ private static XDocument ParseXml(ISymbol docsSymbol, string xmlDocumentation)

private CSharpType? GetNullableCSharpType(ITypeSymbol typeSymbol)
{
var fullyQualifiedName = GetFullyQualifiedName(typeSymbol);
var fullyQualifiedName = typeSymbol.GetFullyQualifiedName();
if (fullyQualifiedName == "System.Void")
{
return null;
}
return GetCSharpType(typeSymbol);
}

private CSharpType GetCSharpType(ITypeSymbol typeSymbol)
{
var fullyQualifiedName = GetFullyQualifiedName(typeSymbol);
var namedTypeSymbol = typeSymbol as INamedTypeSymbol;

Type? type = LoadFrameworkType(fullyQualifiedName);

if (type is null)
{
return ConstructCSharpTypeFromSymbol(typeSymbol, fullyQualifiedName, namedTypeSymbol);
}

CSharpType result = new CSharpType(type);
if (namedTypeSymbol is not null && namedTypeSymbol.IsGenericType && !result.IsNullable)
{
return result.MakeGenericType([.. namedTypeSymbol.TypeArguments.Select(GetCSharpType)]);
}

return result;
}

private static Type? LoadFrameworkType(string fullyQualifiedName)
{
return fullyQualifiedName switch
{
// 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)
};
}

private CSharpType ConstructCSharpTypeFromSymbol(
ITypeSymbol typeSymbol,
string fullyQualifiedName,
INamedTypeSymbol? namedTypeSymbol)
{
var typeArg = namedTypeSymbol?.TypeArguments.FirstOrDefault();
bool isValueType = typeSymbol.IsValueType;
bool isEnum = typeSymbol.TypeKind == TypeKind.Enum;
bool isNullable = typeSymbol.NullableAnnotation == NullableAnnotation.Annotated;
bool isNullableUnknownType = isNullable && typeArg?.TypeKind == TypeKind.Error;
string name = isNullableUnknownType ? fullyQualifiedName : typeSymbol.Name;
string[] pieces = fullyQualifiedName.Split('.');

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

return new CSharpType(
name,
string.Join('.', pieces.Take(pieces.Length - 1)),
isValueType,
isNullable,
typeSymbol.ContainingType is not null ? GetCSharpType(typeSymbol.ContainingType) : null,
namedTypeSymbol is not null && !isNullableUnknownType ? [.. namedTypeSymbol.TypeArguments.Select(GetCSharpType)] : [],
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
: null);
}

private static string GetFullyQualifiedName(ITypeSymbol typeSymbol)
{
// Handle special cases for built-in types
switch (typeSymbol.SpecialType)
{
case SpecialType.System_Object:
return "System.Object";
case SpecialType.System_Void:
return "System.Void";
case SpecialType.System_Boolean:
return "System.Boolean";
case SpecialType.System_Char:
return "System.Char";
case SpecialType.System_SByte:
return "System.SByte";
case SpecialType.System_Byte:
return "System.Byte";
case SpecialType.System_Int16:
return "System.Int16";
case SpecialType.System_UInt16:
return "System.UInt16";
case SpecialType.System_Int32:
return "System.Int32";
case SpecialType.System_UInt32:
return "System.UInt32";
case SpecialType.System_Int64:
return "System.Int64";
case SpecialType.System_UInt64:
return "System.UInt64";
case SpecialType.System_Decimal:
return "System.Decimal";
case SpecialType.System_Single:
return "System.Single";
case SpecialType.System_Double:
return "System.Double";
case SpecialType.System_String:
return "System.String";
case SpecialType.System_DateTime:
return "System.DateTime";
}

// Handle array types
if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol)
{
return GetFullyQualifiedName(arrayTypeSymbol.ElementType) + "[]";
}

// Handle generic types
if (typeSymbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.IsGenericType)
{
// Handle nullable types
if (typeSymbol.NullableAnnotation == NullableAnnotation.Annotated && !IsCollectionType(namedTypeSymbol))
{
const string nullableTypeName = "System.Nullable";
var argTypeSymbol = namedTypeSymbol.TypeArguments.FirstOrDefault();

if (argTypeSymbol != null)
{
if (argTypeSymbol.TypeKind == TypeKind.Error)
{
return GetFullyQualifiedName(argTypeSymbol);
}

string[] typeArguments = [.. namedTypeSymbol.TypeArguments.Select(arg => "[" + GetFullyQualifiedName(arg) + "]")];
return $"{nullableTypeName}`{namedTypeSymbol.TypeArguments.Length}[{string.Join(", ", typeArguments)}]";
}
}
else if (namedTypeSymbol.TypeArguments.Length > 0 && !IsCollectionType(namedTypeSymbol))
{
return GetNonNullableGenericTypeName(namedTypeSymbol);
}

var typeNameSpan = namedTypeSymbol.ConstructedFrom.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat).AsSpan();
var start = typeNameSpan.IndexOf(':') + 2;
var end = typeNameSpan.IndexOf('<');
typeNameSpan = typeNameSpan.Slice(start, end - start);
return $"{typeNameSpan}`{namedTypeSymbol.TypeArguments.Length}";
}

// Default to fully qualified name
return GetFullyQualifiedNameFromDisplayString(typeSymbol);
}

private static string GetNonNullableGenericTypeName(INamedTypeSymbol namedTypeSymbol)
{
string[] typeArguments = [.. namedTypeSymbol.TypeArguments.Select(GetFullyQualifiedName)];
var fullName = namedTypeSymbol.ConstructedFrom.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);

// Remove the type arguments from the fully qualified name
var typeArgumentStartIndex = fullName.IndexOf('<');
var genericTypeName = typeArgumentStartIndex >= 0 ? fullName.Substring(0, typeArgumentStartIndex) : fullName;

// Remove global:: prefix
if (genericTypeName.StartsWith(GlobalPrefix, StringComparison.Ordinal))
{
genericTypeName = genericTypeName.Substring(GlobalPrefix.Length);
}

return $"{genericTypeName}`{namedTypeSymbol.TypeArguments.Length}[{string.Join(", ", typeArguments)}]";
}

private static bool IsCollectionType(INamedTypeSymbol typeSymbol)
{
// Check if the type implements IEnumerable<T>, ICollection<T>, or IEnumerable
return typeSymbol.AllInterfaces.Any(i =>
i.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T ||
i.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_ICollection_T ||
i.OriginalDefinition.SpecialType == SpecialType.System_Collections_IEnumerable);
}

private static string GetFullyQualifiedNameFromDisplayString(ISymbol typeSymbol)
{
var fullyQualifiedName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
return fullyQualifiedName.StartsWith(GlobalPrefix, StringComparison.Ordinal) ? fullyQualifiedName.Substring(GlobalPrefix.Length) : fullyQualifiedName;
return typeSymbol.GetCSharpType();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ private static bool IsMatch(TypeProvider enclosingType, MethodSignatureBase sign
}
else if (attribute.ConstructorArguments[1].Kind != TypedConstantKind.Array)
{
parameterTypes = [(ISymbol?) attribute.ConstructorArguments[1].Value];
parameterTypes = attribute.ConstructorArguments[1..].Select(a => (ISymbol?) a.Value).ToArray();
}
else
{
Expand All @@ -437,7 +437,8 @@ private static bool IsMatch(TypeProvider enclosingType, MethodSignatureBase sign

for (int i = 0; i < parameterTypes.Length; i++)
{
if (parameterTypes[i]?.Name != signature.Parameters[i].Type.Name)
var parameterType = ((ITypeSymbol)parameterTypes[i]!).GetCSharpType();
if (parameterType.Name != signature.Parameters[i].Type.Name || parameterType.IsNullable != signature.Parameters[i].Type.IsNullable)
{
return false;
}
Expand Down

This file was deleted.

Loading

0 comments on commit 9ab01ec

Please sign in to comment.