Skip to content

Commit 62c2281

Browse files
VS-93: Address Handling of Nullables (#44)
* VS-93 : Address Handling of Nullables Co-authored-by: BorisDog <[email protected]>
1 parent f2fe2e6 commit 62c2281

File tree

16 files changed

+588
-118
lines changed

16 files changed

+588
-118
lines changed

src/MongoDB.Analyzer.Helpers/Builders/MqlGenerator.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
using System;
1516
using System.Linq;
1617
using MongoDB.Bson;
1718
using MongoDB.Bson.Serialization.Options;
@@ -23,8 +24,9 @@ public static class MqlGenerator
2324
{
2425
#pragma warning disable CS0169 // These fields are never used, they are needed to ensure that the relevant usings are not accidently removed
2526
#pragma warning disable IDE0051
26-
private static readonly BsonType s_dummyRef1;
27-
private static readonly TimeSpanUnits s_dummyRef2;
27+
private static readonly Int32 s_dummyRef1; // using System
28+
private static readonly BsonType s_dummyRef2; // using MongoDB.Bson
29+
private static readonly TimeSpanUnits s_dummyRef3; // using MongoDB.Bson.Serialization.Option
2830
#pragma warning restore IDE0051 // These fields are never used, they are needed to ensure that the relevant usings are not accidently removed
2931
#pragma warning restore CS0169
3032

src/MongoDB.Analyzer.Helpers/Linq/MqlGenerator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ public static class MqlGenerator
2424
{
2525
#pragma warning disable CS0169 // These fields are never used, they are needed to ensure that the relevant usings are not accidently removed
2626
#pragma warning disable IDE0051
27-
private static readonly BsonType s_dummyRef1;
28-
private static readonly TimeSpanUnits s_dummyRef2;
27+
private static readonly BsonType s_dummyRef1; // using MongoDB.Bson
28+
private static readonly TimeSpanUnits s_dummyRef2; // using MongoDB.Bson.Serialization.Option
2929
#pragma warning restore IDE0051 // These fields are never used, they are needed to ensure that the relevant usings are not accidently removed
3030
#pragma warning restore CS0169
3131

src/MongoDB.Analyzer/Core/Builders/BuilderExpressionProcessor.cs

Lines changed: 15 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ private static RewriteResult HandleField(
302302
SyntaxNode replacementNode;
303303
if (fieldSymbol.Type.TypeKind == TypeKind.Enum)
304304
{
305-
replacementNode = GetEnumCastNode(fieldSymbol.Type, fieldSymbol.ConstantValue, rewriteContext.TypesProcessor);
305+
replacementNode = ExpressionProcessorUtilities.GetEnumCastNode(fieldSymbol.Type, fieldSymbol.ConstantValue, rewriteContext.TypesProcessor);
306306
}
307307
else if (fieldSymbol.Type.SpecialType != SpecialType.None)
308308
{
@@ -335,7 +335,11 @@ private static RewriteResult HandleMethod(
335335

336336
var typeSymbol = typeInfo.ConvertedType ?? methodSymbol.ReturnType;
337337
var nodeToReplace = SyntaxFactoryUtilities.ResolveAccessExpressionNode(simpleNameSyntax);
338-
var replacementNode = GetConstantReplacementNode(rewriteContext, typeSymbol, nodeToReplace.ToString());
338+
var replacementNode = ExpressionProcessorUtilities.GetConstantReplacementNode(
339+
rewriteContext.TypesProcessor,
340+
rewriteContext.ConstantsMapper,
341+
typeSymbol,
342+
nodeToReplace.ToString());
339343

340344
if (replacementNode == null)
341345
{
@@ -345,40 +349,6 @@ private static RewriteResult HandleMethod(
345349
return new RewriteResult(nodeToReplace, replacementNode);
346350
}
347351

348-
private static SyntaxNode GetEnumCastNode(ITypeSymbol typeSymbol, object constantValue, TypesProcessor typesProcessor)
349-
{
350-
var remappedEnumTypeName = typesProcessor.GetTypeSymbolToGeneratedTypeMapping(typeSymbol);
351-
352-
if (remappedEnumTypeName.IsNullOrWhiteSpace())
353-
{
354-
return null;
355-
}
356-
357-
return SyntaxFactoryUtilities.GetCastConstantExpression(remappedEnumTypeName, constantValue);
358-
}
359-
360-
private static SyntaxNode GetConstantReplacementNode(
361-
RewriteContext rewriteContext,
362-
ITypeSymbol typeSymbol,
363-
string fullName = null)
364-
{
365-
SyntaxNode replacementNode = null;
366-
367-
if (typeSymbol.TypeKind == TypeKind.Enum)
368-
{
369-
var underlyingEnumType = (typeSymbol as INamedTypeSymbol).EnumUnderlyingType.SpecialType;
370-
371-
var literalSyntax = rewriteContext.ConstantsMapper.GetExpressionByType(underlyingEnumType, fullName);
372-
replacementNode = GetEnumCastNode(typeSymbol, literalSyntax.Token.Value, rewriteContext.TypesProcessor);
373-
}
374-
else if (typeSymbol.SpecialType != SpecialType.None)
375-
{
376-
replacementNode = rewriteContext.ConstantsMapper.GetExpressionByType(typeSymbol.SpecialType, fullName);
377-
}
378-
379-
return replacementNode;
380-
}
381-
382352
private static bool IsChildOfLambdaParameterOrBuilders(
383353
RewriteContext rewriteContext,
384354
SyntaxNode simpleNameSyntax,
@@ -389,14 +359,14 @@ private static bool IsChildOfLambdaParameterOrBuilders(
389359
return true;
390360
}
391361

392-
var underlyingIdetifier = SyntaxFactoryUtilities.GetUnderlyingIdentifier(simpleNameSyntax);
393-
if (underlyingIdetifier == null)
362+
var underlyingIdentifier = SyntaxFactoryUtilities.GetUnderlyingIdentifier(simpleNameSyntax);
363+
if (underlyingIdentifier == null)
394364
{
395365
return false;
396366
}
397367

398-
if (underlyingIdetifier.Identifier.Text == "Builders" ||
399-
rewriteContext.SemanticModel.GetSymbolInfo(underlyingIdetifier).Symbol.IsContainedInLambda(rewriteContext.BuildersExpression))
368+
if (underlyingIdentifier.Identifier.Text == "Builders" ||
369+
rewriteContext.SemanticModel.GetSymbolInfo(underlyingIdentifier).Symbol.IsContainedInLambda(rewriteContext.BuildersExpression))
400370
{
401371
return true;
402372
}
@@ -585,7 +555,11 @@ private static RewriteResult SubstituteExpressionWithConst(
585555
}
586556

587557
var nodeToReplace = SyntaxFactoryUtilities.ResolveAccessExpressionNode(simpleNameSyntax);
588-
var replacementNode = GetConstantReplacementNode(rewriteContext, typeInfo.Type, nodeToReplace.ToString());
558+
var replacementNode = ExpressionProcessorUtilities.GetConstantReplacementNode(
559+
rewriteContext.TypesProcessor,
560+
rewriteContext.ConstantsMapper,
561+
typeInfo.Type,
562+
nodeToReplace.ToString());
589563

590564
if (replacementNode == null)
591565
{

src/MongoDB.Analyzer/Core/Linq/LinqExpressionProcessor.cs

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ private static RewriteResult HandleField(
349349
SyntaxNode replacementNode;
350350
if (fieldSymbol.Type.TypeKind == TypeKind.Enum)
351351
{
352-
replacementNode = GetEnumCastNode(fieldSymbol.Type, fieldSymbol.ConstantValue, rewriteContext.TypesProcessor);
352+
replacementNode = ExpressionProcessorUtilities.GetEnumCastNode(fieldSymbol.Type, fieldSymbol.ConstantValue, rewriteContext.TypesProcessor);
353353
}
354354
else if (fieldSymbol.Type.SpecialType != SpecialType.None)
355355
{
@@ -378,7 +378,11 @@ private static RewriteResult HandleMethod(
378378
}
379379

380380
var nodeToReplace = SyntaxFactoryUtilities.ResolveAccessExpressionNode(identifierNode);
381-
var replacementNode = GetConstantReplacementNode(rewriteContext, methodSymbol.ReturnType, nodeToReplace.ToString());
381+
var replacementNode = ExpressionProcessorUtilities.GetConstantReplacementNode(
382+
rewriteContext.TypesProcessor,
383+
rewriteContext.ConstantsMapper,
384+
methodSymbol.ReturnType,
385+
nodeToReplace.ToString());
382386

383387
if (replacementNode == null)
384388
{
@@ -388,40 +392,6 @@ private static RewriteResult HandleMethod(
388392
return new RewriteResult(nodeToReplace, replacementNode);
389393
}
390394

391-
private static SyntaxNode GetEnumCastNode(ITypeSymbol typeSymbol, object constantValue, TypesProcessor typesProcessor)
392-
{
393-
var remappedEnumTypeName = typesProcessor.GetTypeSymbolToGeneratedTypeMapping(typeSymbol);
394-
395-
if (remappedEnumTypeName.IsNullOrWhiteSpace())
396-
{
397-
return null;
398-
}
399-
400-
return SyntaxFactoryUtilities.GetCastConstantExpression(remappedEnumTypeName, constantValue);
401-
}
402-
403-
private static SyntaxNode GetConstantReplacementNode(
404-
RewriteContext rewriteContext,
405-
ITypeSymbol typeSymbol,
406-
string fullName = null)
407-
{
408-
SyntaxNode replacementNode = null;
409-
410-
if (typeSymbol.TypeKind == TypeKind.Enum)
411-
{
412-
var underlyingEnumType = (typeSymbol as INamedTypeSymbol).EnumUnderlyingType.SpecialType;
413-
414-
var literalSyntax = rewriteContext.ConstantsMapper.GetExpressionByType(underlyingEnumType, fullName);
415-
replacementNode = GetEnumCastNode(typeSymbol, literalSyntax.Token.Value, rewriteContext.TypesProcessor);
416-
}
417-
else if (typeSymbol.SpecialType != SpecialType.None)
418-
{
419-
replacementNode = rewriteContext.ConstantsMapper.GetExpressionByType(typeSymbol.SpecialType, fullName);
420-
}
421-
422-
return replacementNode;
423-
}
424-
425395
private static bool IsChildOfLambdaOrQueryParameter(
426396
RewriteContext rewriteContext,
427397
SyntaxNode identifier,
@@ -460,7 +430,11 @@ private static RewriteResult SubstituteExpressionWithConst(
460430
}
461431

462432
var nodeToReplace = SyntaxFactoryUtilities.ResolveAccessExpressionNode(identifierNode);
463-
var replacementNode = GetConstantReplacementNode(rewriteContext, typeInfo.ConvertedType, nodeToReplace.ToString());
433+
var replacementNode = ExpressionProcessorUtilities.GetConstantReplacementNode(
434+
rewriteContext.TypesProcessor,
435+
rewriteContext.ConstantsMapper,
436+
typeInfo.ConvertedType,
437+
nodeToReplace.ToString());
464438

465439
if (replacementNode == null)
466440
{

src/MongoDB.Analyzer/Core/TypesProcessor.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,11 @@ private TypeSyntax CreateTypeSyntaxForSymbol(ITypeSymbol typeSymbol)
283283
}
284284
else
285285
{
286-
result = SyntaxFactory.ParseTypeName(ProcessTypeSymbol(typeSymbol));
286+
var (isNullable, underlingTypeSymbol) = typeSymbol.DiscardNullable();
287+
288+
var newTypeName = ProcessTypeSymbol(underlingTypeSymbol);
289+
result = isNullable ? SyntaxFactoryUtilities.GetNullableType(newTypeName) :
290+
SyntaxFactory.ParseTypeName(newTypeName);
287291
}
288292

289293
return result;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2021-present MongoDB Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License")
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace MongoDB.Analyzer.Core;
16+
17+
internal static class ExpressionProcessorUtilities
18+
{
19+
public static SyntaxNode GetConstantReplacementNode(
20+
TypesProcessor typesProcessor,
21+
ConstantsMapper constantsMapper,
22+
ITypeSymbol typeSymbol,
23+
string originalNodeFullName = null)
24+
{
25+
ExpressionSyntax replacementNode = null;
26+
var (isNullable, underlingTypeSymbol) = typeSymbol.DiscardNullable();
27+
28+
if (underlingTypeSymbol.TypeKind == TypeKind.Enum)
29+
{
30+
var underlyingEnumType = (underlingTypeSymbol as INamedTypeSymbol).EnumUnderlyingType.SpecialType;
31+
var literalSyntax = constantsMapper.GetExpressionByType(underlyingEnumType, originalNodeFullName);
32+
replacementNode = GetEnumCastNode(underlingTypeSymbol, literalSyntax.Token.Value, typesProcessor, isNullable);
33+
}
34+
else if (underlingTypeSymbol.SpecialType != SpecialType.None)
35+
{
36+
replacementNode = constantsMapper.GetExpressionByType(underlingTypeSymbol.SpecialType, originalNodeFullName);
37+
38+
if (isNullable)
39+
{
40+
replacementNode = SyntaxFactory.CastExpression(
41+
SyntaxFactoryUtilities.GetNullableType(underlingTypeSymbol.Name),
42+
replacementNode);
43+
}
44+
}
45+
46+
return replacementNode;
47+
}
48+
49+
public static ExpressionSyntax GetEnumCastNode(
50+
ITypeSymbol typeSymbol,
51+
object constantValue,
52+
TypesProcessor typesProcessor,
53+
bool isNullable = false)
54+
{
55+
var remappedEnumTypeName = typesProcessor.GetTypeSymbolToGeneratedTypeMapping(typeSymbol);
56+
57+
if (remappedEnumTypeName.IsNullOrWhiteSpace())
58+
{
59+
return null;
60+
}
61+
62+
return SyntaxFactoryUtilities.GetCastConstantExpression(remappedEnumTypeName, constantValue, isNullable);
63+
}
64+
}

src/MongoDB.Analyzer/Core/Utilities/SymbolExtensions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ internal static class SymbolExtensions
6969
"System.Type"
7070
};
7171

72+
public static (bool IsNullable, ITypeSymbol underlyingType) DiscardNullable(this ITypeSymbol typeSymbol) =>
73+
typeSymbol?.OriginalDefinition.SpecialType switch
74+
{
75+
SpecialType.System_Nullable_T => (true, ((INamedTypeSymbol)typeSymbol).TypeArguments.SingleOrDefault()),
76+
_ => (false, typeSymbol)
77+
};
78+
7279
public static SyntaxToken[] GetFieldModifiers(this IFieldSymbol fieldSymbol) =>
7380
fieldSymbol.IsReadOnly ? GetReadOnlyPublicFieldModifiers() : GetPublicFieldModifiers();
7481

@@ -194,7 +201,7 @@ typeSymbol is INamedTypeSymbol namedTypeSymbol &&
194201
public static bool IsSupportedMongoCollectionType(this ITypeSymbol typeSymbol) =>
195202
typeSymbol.TypeKind == TypeKind.Class &&
196203
!typeSymbol.IsAnonymousType;
197-
204+
198205
public static bool IsSupportedSystemType(this ITypeSymbol typeSymbol, string fullTypeName) =>
199206
(typeSymbol.SpecialType != SpecialType.None || s_supportedSystemTypes.Contains(fullTypeName)) &&
200207
typeSymbol?.ContainingNamespace?.ToDisplayString() == NamespaceSystem;

src/MongoDB.Analyzer/Core/Utilities/SyntaxFactoryUtilities.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ public static ArrayCreationExpressionSyntax GetArrayCreationExpression(ArrayType
3434
SyntaxFactory.ArrayCreationExpression(SyntaxFactory.Token(SyntaxKind.NewKeyword), arrayTypeSyntax,
3535
SyntaxFactory.InitializerExpression(SyntaxKind.ArrayInitializerExpression, SyntaxFactory.SeparatedList<ExpressionSyntax>(expressions)));
3636

37-
public static CastExpressionSyntax GetCastConstantExpression(string castToTypeName, object constantValue) =>
38-
SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName(castToTypeName), GetConstantExpression(constantValue));
37+
public static CastExpressionSyntax GetCastConstantExpression(string castToTypeName, object constantValue, bool isCastToTypeNullable = false) =>
38+
SyntaxFactory.CastExpression(
39+
isCastToTypeNullable ? GetNullableType(castToTypeName) : SyntaxFactory.ParseTypeName(castToTypeName),
40+
GetConstantExpression(constantValue));
3941

4042
public static LiteralExpressionSyntax GetConstantExpression(object constantValue) =>
4143
constantValue switch
@@ -61,6 +63,9 @@ public static SyntaxToken GetConstantValueToken(object value) =>
6163
_ => throw new NotSupportedException($"Not supported type {value?.GetType()}")
6264
};
6365

66+
public static TypeSyntax GetNullableType(string typeName) =>
67+
SyntaxFactory.NullableType(SyntaxFactory.ParseTypeName(typeName));
68+
6469
public static SpecialType GetSpecialType(object value) =>
6570
value switch
6671
{

0 commit comments

Comments
 (0)