Skip to content
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