Skip to content

VS-153: Enable TypesProcessor to Process Dictionaries #77

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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ public static ExpressionsAnalysis ProcessSemanticModel(MongoAnalysisContext cont

try
{
foreach (var typeArgument in namedType.TypeArguments)
if (!namedType.TypeArguments.All(t => typesProcessor.ProcessTypeSymbol(t) != null))
{
typesProcessor.ProcessTypeSymbol(typeArgument);
continue;
}

var rewriteContext = RewriteContext.Builders(expressionNode, nodesToRewrite, semanticModel, typesProcessor);
Expand Down
4 changes: 4 additions & 0 deletions src/MongoDB.Analyzer/Core/Linq/LinqExpressionProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ mongoQueryableTypeInfo.Type is not INamedTypeSymbol mongoQueryableNamedType ||
(analysisType == AnalysisType.EF && PreanalyzeEFExpression(node, semanticModel, invalidExpressionNodes, mongoQueryableNamedType)))
{
var generatedMongoQueryableTypeName = typesProcessor.ProcessTypeSymbol(mongoQueryableNamedType.TypeArguments[0]);
if (generatedMongoQueryableTypeName == null)
{
continue;
}

var rewriteContext = RewriteContext.Linq(node, deepestMongoQueryableNode, semanticModel, typesProcessor);
var (newLinqExpression, constantsMapper) = RewriteExpression(rewriteContext);
Expand Down
5 changes: 5 additions & 0 deletions src/MongoDB.Analyzer/Core/Poco/PocoExpressionProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ public static ExpressionsAnalysis ProcessSemanticModel(MongoAnalysisContext cont
if (PreanalyzeClassDeclaration(context, classSymbol))
{
var generatedClassName = typesProcessor.ProcessTypeSymbol(classSymbol);
if (generatedClassName == null)
{
continue;
}

var generatedClassNode = (ClassDeclarationSyntax)(typesProcessor.GetTypeSymbolToMemberDeclarationMapping(classSymbol));
var expressionContext = new ExpressionAnalysisContext(new ExpressionAnalysisNode(classNode, null, generatedClassNode, null, classNode.GetLocation()));
analysisContexts.Add(expressionContext);
Expand Down
1 change: 1 addition & 0 deletions src/MongoDB.Analyzer/Core/ReferencesProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public static ReferencesContainer GetReferences(IEnumerable<MetadataReference> m
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(IEnumerator<int>).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Queryable).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Stack<int>).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(System.Dynamic.DynamicObject).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location));
resultReferences.Add(MetadataReference.CreateFromFile(typeof(Task).Assembly.Location));
Expand Down
97 changes: 79 additions & 18 deletions src/MongoDB.Analyzer/Core/TypesProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,28 @@ public string ProcessTypeSymbol(ITypeSymbol typeSymbol)
}

remappedName = GetNewNameForSymbol(typeSymbol);
BaseTypeDeclarationSyntax rewrittenDeclarationSyntax;

_processedTypes[fullTypeName] = (remappedName, null); // Cache the name, for self referencing types

var rewrittenDeclarationSyntax = GetSyntaxNodeFromSymbol(typeSymbol, remappedName);
try
{
rewrittenDeclarationSyntax = GetSyntaxNodeFromSymbol(typeSymbol, remappedName);
}
catch
{
_processedTypes.Remove(fullTypeName);
throw;
}

var typeCode = rewrittenDeclarationSyntax.ToFullString();
var newTypeDeclaration = SyntaxFactory.ParseMemberDeclaration(typeCode);
if (rewrittenDeclarationSyntax == null)
{
_processedTypes.Remove(fullTypeName);
return null;
}

remappedName = rewrittenDeclarationSyntax.Identifier.Text;
_processedTypes[fullTypeName] = (remappedName, newTypeDeclaration);
_processedTypes[fullTypeName] = (remappedName, rewrittenDeclarationSyntax);

return remappedName;
}
Expand All @@ -93,31 +106,48 @@ private TypeSyntax CreateTypeSyntaxForSymbol(ITypeSymbol typeSymbol)
arrayRankSpecifiers = SyntaxFactory.List(new[] { SyntaxFactory.ArrayRankSpecifier(ranksList) });
var nextTypeSyntax = CreateTypeSyntaxForSymbol(arrayTypeSymbol.ElementType);

if (nextTypeSyntax == null)
{
return null;
}

result = SyntaxFactory.ArrayType(nextTypeSyntax, arrayRankSpecifiers.Value);
}
// TODO optimize
else if (typeSymbol is INamedTypeSymbol namedTypeSymbol &&
namedTypeSymbol.TypeArguments.Length == 1 &&
namedTypeSymbol.IsSupportedCollection())
namedTypeSymbol.TypeArguments.Length >= 1 &&
typeSymbol.IsSystemCollection())
{
var underlyingTypeSyntax = CreateTypeSyntaxForSymbol(namedTypeSymbol.TypeArguments.Single());
var listSyntax = SyntaxFactory.GenericName(
SyntaxFactory.Identifier("List"),
SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(new[] { underlyingTypeSyntax })));
var underlyingTypeSyntaxes = namedTypeSymbol.TypeArguments.Select(typeArgument => CreateTypeSyntaxForSymbol(typeArgument));
if (underlyingTypeSyntaxes.Any(underlyingTypeSyntax => underlyingTypeSyntax == null))
{
return null;
}

var collectionIdentifier = namedTypeSymbol.Name;

var collectionSyntax = SyntaxFactory.GenericName(
SyntaxFactory.Identifier(collectionIdentifier),
SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(underlyingTypeSyntaxes)));

result = SyntaxFactory.QualifiedName(
SyntaxFactory.QualifiedName(
SyntaxFactory.QualifiedName(
SyntaxFactory.IdentifierName("System"),
SyntaxFactory.IdentifierName("Collections")),
SyntaxFactory.IdentifierName("Generic")),
listSyntax);
collectionSyntax);
}
else
{
var (isNullable, underlingTypeSymbol) = typeSymbol.DiscardNullable();

var newTypeName = ProcessTypeSymbol(underlingTypeSymbol);

if (newTypeName == null)
{
return null;
}

result = isNullable ? SyntaxFactoryUtilities.GetNullableType(newTypeName) :
SyntaxFactory.ParseTypeName(newTypeName);
}
Expand Down Expand Up @@ -171,19 +201,23 @@ private ExpressionSyntax GenerateExpressionFromBsonAttributeArgumentInfo(TypedCo
_ => null
};

private void GenerateFields(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
private bool GenerateFields(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
{
var typeFields = typeSymbol
.GetMembers()
.OfType<IFieldSymbol>()
.Where(p =>
!p.IsStatic &&
(p.Type.TypeKind != TypeKind.Interface || p.Type.IsSupportedCollection()) &&
(p.Type.TypeKind != TypeKind.Interface || p.Type.IsSystemCollection()) &&
p.DeclaredAccessibility == Accessibility.Public);

foreach (var fieldSymbol in typeFields)
{
var typeSyntax = CreateTypeSyntaxForSymbol(fieldSymbol.Type);
if (typeSyntax == null)
{
return false;
}

var variableDeclaration = SyntaxFactory.VariableDeclaration(typeSyntax, SyntaxFactory.SingletonSeparatedList(SyntaxFactory.VariableDeclarator(fieldSymbol.Name)));

Expand All @@ -199,22 +233,28 @@ private void GenerateFields(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax

members.Add(fieldDeclaration);
}

return true;
}

private void GenerateProperties(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
private bool GenerateProperties(ITypeSymbol typeSymbol, List<MemberDeclarationSyntax> members)
{
var typeProperties = typeSymbol
.GetMembers()
.OfType<IPropertySymbol>()
.Where(p =>
!p.IsStatic &&
!p.IsIndexer &&
(p.Type.TypeKind != TypeKind.Interface || p.Type.IsSupportedCollection()) &&
(p.Type.TypeKind != TypeKind.Interface || p.Type.IsSystemCollection()) &&
p.DeclaredAccessibility == Accessibility.Public);

foreach (var propertySymbol in typeProperties)
{
var typeSyntax = CreateTypeSyntaxForSymbol(propertySymbol.Type);
if (typeSyntax == null)
{
return false;
}

var propertyDeclaration = SyntaxFactory.PropertyDeclaration(typeSyntax, propertySymbol.Name);

Expand All @@ -229,6 +269,8 @@ private void GenerateProperties(ITypeSymbol typeSymbol, List<MemberDeclarationSy

members.Add(propertyDeclaration);
}

return true;
}

private BaseListSyntax GetBaseListSyntax(string typeName)
Expand Down Expand Up @@ -265,6 +307,12 @@ private string GetFullName(ITypeSymbol typeSymbol) =>
return (fullTypeName, fullTypeName);
}

if (!userOnlyTypes && typeSymbol.IsSystemCollection(includeBaseTypesAndInterfaces: true))
{
// Types derived from System.Collections.Generic are not supported
return default;
}

return (null, fullTypeName);
}

Expand All @@ -285,8 +333,11 @@ private TypeDeclarationSyntax GetSyntaxForClassOrStruct(ITypeSymbol typeSymbol,

var members = new List<MemberDeclarationSyntax>();

GenerateProperties(typeSymbol, members);
GenerateFields(typeSymbol, members);
if (!GenerateProperties(typeSymbol, members) ||
!GenerateFields(typeSymbol, members))
{
return null;
}

typeDeclaration = typeDeclaration
.AddMembers(members.ToArray())
Expand All @@ -298,6 +349,11 @@ private TypeDeclarationSyntax GetSyntaxForClassOrStruct(ITypeSymbol typeSymbol,
{
var baseTypeNameGenerated = ProcessTypeSymbol(typeSymbol.BaseType);

if (baseTypeNameGenerated == null)
{
return null;
}

typeDeclaration = typeDeclaration.WithBaseList(GetBaseListSyntax(baseTypeNameGenerated));
}

Expand Down Expand Up @@ -351,6 +407,11 @@ private BaseTypeDeclarationSyntax GetSyntaxNodeFromSymbol(ITypeSymbol typeSymbol
throw new NotSupportedException($"Symbol type {typeSymbol.TypeKind} is not supported.");
}

if (typeDeclaration == null)
{
return null;
}

typeDeclaration = typeDeclaration.NormalizeWhitespace();
return typeDeclaration;
}
Expand Down
59 changes: 30 additions & 29 deletions src/MongoDB.Analyzer/Core/Utilities/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace MongoDB.Analyzer.Core;
internal static class SymbolExtensions
{
private const string AssemblyMongoDBDriver = "MongoDB.Driver";
private const string NamespaceCollectionGeneric = "System.Collections.Generic";
private const string NamespaceEF = "Microsoft.EntityFrameworkCore";
private const string NamespaceMongoDBBson = "MongoDB.Bson";
private const string NamespaceMongoDBBsonAttributes = "MongoDB.Bson.Serialization.Attributes";
Expand Down Expand Up @@ -54,13 +55,6 @@ internal static class SymbolExtensions
"MongoDB.Bson.Serialization.Options.TimeSpanUnits"
};

private static readonly HashSet<string> s_supportedCollections = new()
{
"System.Collections.Generic.IEnumerable<T>",
"System.Collections.Generic.IList<T>",
"System.Collections.Generic.List<T>"
};

private static readonly HashSet<string> s_supportedSystemTypes = new()
{
"System.DateTime",
Expand Down Expand Up @@ -160,7 +154,8 @@ public static bool IsDBSet(this ITypeSymbol typeSymbol) =>
typeSymbol?.Name == "DbSet" &&
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceEF;

public static bool IsDefinedInMongoDriver(this ISymbol symbol) => symbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;
public static bool IsDefinedInMongoDriver(this ISymbol symbol) => symbol?.ContainingNamespace?.ToDisplayString() == NamespaceMongoDBDriver &&
symbol?.ContainingAssembly.Name == AssemblyMongoDBDriver;

public static bool IsDefinedInMongoLinqOrSystemLinq(this ISymbol symbol)
{
Expand Down Expand Up @@ -222,44 +217,50 @@ TypeKind.Enum or
_ => false
};

public static bool IsSupportedCollection(this ITypeSymbol typeSymbol)
public static bool IsSupportedIMongoCollection(this ITypeSymbol typeSymbol) =>
typeSymbol.IsIMongoCollection() &&
typeSymbol is INamedTypeSymbol namedType &&
namedType.TypeArguments.Length == 1 &&
namedType.TypeArguments[0].IsSupportedMongoCollectionType();

public static bool IsSupportedMongoCollectionType(this ITypeSymbol typeSymbol) =>
typeSymbol.TypeKind == TypeKind.Class &&
!typeSymbol.IsAnonymousType;

public static bool IsSupportedSystemType(this ITypeSymbol typeSymbol, string fullTypeName) =>
(typeSymbol.SpecialType != SpecialType.None || s_supportedSystemTypes.Contains(fullTypeName)) &&
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceSystem;

public static bool IsSystemCollection(this ITypeSymbol typeSymbol, bool includeBaseTypesAndInterfaces = false)
{
if (typeSymbol is not INamedTypeSymbol namedTypeSymbol)
{
return false;
return default;
}

if (!includeBaseTypesAndInterfaces)
{
return namedTypeSymbol.ContainingNamespace?.ToDisplayString() == NamespaceCollectionGeneric;
}

if (namedTypeSymbol.AllInterfaces.Any(interfaceSymbol => interfaceSymbol.ContainingNamespace?.ToDisplayString() == NamespaceCollectionGeneric))
{
return true;
}

while (namedTypeSymbol != null)
{
if (s_supportedCollections.Contains(namedTypeSymbol.ConstructedFrom?.ToDisplayString()))
if (namedTypeSymbol.ContainingNamespace?.ToDisplayString() == NamespaceCollectionGeneric)
{
return true;
}

if (namedTypeSymbol.Interfaces.Any(i => s_supportedCollections.Contains(i.ConstructedFrom?.ToDisplayString()))){
return true;
}

namedTypeSymbol = namedTypeSymbol.BaseType;
}

return false;
}

public static bool IsSupportedIMongoCollection(this ITypeSymbol typeSymbol) =>
typeSymbol.IsIMongoCollection() &&
typeSymbol is INamedTypeSymbol namedType &&
namedType.TypeArguments.Length == 1 &&
namedType.TypeArguments[0].IsSupportedMongoCollectionType();

public static bool IsSupportedMongoCollectionType(this ITypeSymbol typeSymbol) =>
typeSymbol.TypeKind == TypeKind.Class &&
!typeSymbol.IsAnonymousType;

public static bool IsSupportedSystemType(this ITypeSymbol typeSymbol, string fullTypeName) =>
(typeSymbol.SpecialType != SpecialType.None || s_supportedSystemTypes.Contains(fullTypeName)) &&
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceSystem;

private static SyntaxToken[] GetPublicFieldModifiers() =>
new[] { SyntaxFactory.Token(SyntaxKind.PublicKeyword) };

Expand Down
Loading