@@ -219,11 +219,13 @@ public override void Initialize(AnalysisContext context)
219219 return ;
220220 }
221221
222- context . RegisterOperationAction ( context => AnalyzeInvocationOperation ( context , assertTypeSymbol ) , OperationKind . Invocation ) ;
222+ INamedTypeSymbol objectTypeSymbol = context . Compilation . GetSpecialType ( SpecialType . System_Object ) ;
223+
224+ context . RegisterOperationAction ( context => AnalyzeInvocationOperation ( context , assertTypeSymbol , objectTypeSymbol ) , OperationKind . Invocation ) ;
223225 } ) ;
224226 }
225227
226- private static void AnalyzeInvocationOperation ( OperationAnalysisContext context , INamedTypeSymbol assertTypeSymbol )
228+ private static void AnalyzeInvocationOperation ( OperationAnalysisContext context , INamedTypeSymbol assertTypeSymbol , INamedTypeSymbol objectTypeSymbol )
227229 {
228230 var operation = ( IInvocationOperation ) context . Operation ;
229231 IMethodSymbol targetMethod = operation . TargetMethod ;
@@ -240,19 +242,19 @@ private static void AnalyzeInvocationOperation(OperationAnalysisContext context,
240242 switch ( targetMethod . Name )
241243 {
242244 case "IsTrue" :
243- AnalyzeIsTrueOrIsFalseInvocation ( context , firstArgument , isTrueInvocation : true ) ;
245+ AnalyzeIsTrueOrIsFalseInvocation ( context , firstArgument , isTrueInvocation : true , objectTypeSymbol ) ;
244246 break ;
245247
246248 case "IsFalse" :
247- AnalyzeIsTrueOrIsFalseInvocation ( context , firstArgument , isTrueInvocation : false ) ;
249+ AnalyzeIsTrueOrIsFalseInvocation ( context , firstArgument , isTrueInvocation : false , objectTypeSymbol ) ;
248250 break ;
249251
250252 case "AreEqual" :
251- AnalyzeAreEqualOrAreNotEqualInvocation ( context , firstArgument , isAreEqualInvocation : true ) ;
253+ AnalyzeAreEqualOrAreNotEqualInvocation ( context , firstArgument , isAreEqualInvocation : true , objectTypeSymbol ) ;
252254 break ;
253255
254256 case "AreNotEqual" :
255- AnalyzeAreEqualOrAreNotEqualInvocation ( context , firstArgument , isAreEqualInvocation : false ) ;
257+ AnalyzeAreEqualOrAreNotEqualInvocation ( context , firstArgument , isAreEqualInvocation : false , objectTypeSymbol ) ;
256258 break ;
257259 }
258260 }
@@ -429,6 +431,7 @@ private static StringMethodCheckStatus RecognizeStringMethodCheck(
429431
430432 private static CollectionCheckStatus RecognizeCollectionMethodCheck (
431433 IOperation operation ,
434+ INamedTypeSymbol objectTypeSymbol ,
432435 out SyntaxNode ? collectionExpression ,
433436 out SyntaxNode ? itemExpression )
434437 {
@@ -439,10 +442,7 @@ private static CollectionCheckStatus RecognizeCollectionMethodCheck(
439442 // Check for Collection.Contains(item)
440443 if ( methodName == "Contains" && invocation . Arguments . Length == 1 )
441444 {
442- // Ensure it's a collection type (implements IEnumerable)
443- ITypeSymbol ? receiverType = invocation . Instance ? . Type ;
444- if ( receiverType is not null &&
445- IsCollectionType ( receiverType ) )
445+ if ( IsBCLCollectionType ( invocation . TargetMethod . ContainingType , objectTypeSymbol ) )
446446 {
447447 collectionExpression = invocation . Instance ? . Syntax ;
448448 itemExpression = invocation . Arguments [ 0 ] . Value . Syntax ;
@@ -456,11 +456,14 @@ private static CollectionCheckStatus RecognizeCollectionMethodCheck(
456456 return CollectionCheckStatus . Unknown ;
457457 }
458458
459- private static bool IsCollectionType ( ITypeSymbol type )
460- // Check if the type implements IEnumerable (but is not string)
459+ private static bool IsBCLCollectionType ( ITypeSymbol type , INamedTypeSymbol objectTypeSymbol )
460+ // Check if the type implements IEnumerable<T> (but is not string)
461+ // Note: Assert.Contains/IsEmpty/HasCount for collections accept IEnumerable<T>, but not IEnumerable.
461462 => type . SpecialType != SpecialType . System_String && type . AllInterfaces . Any ( i =>
462- i . SpecialType == SpecialType . System_Collections_IEnumerable ||
463- ( i . OriginalDefinition ? . SpecialType == SpecialType . System_Collections_Generic_IEnumerable_T ) ) ;
463+ i . OriginalDefinition . SpecialType == SpecialType . System_Collections_Generic_IEnumerable_T ) &&
464+ // object is coming from BCL and it's expected to always have a public key.
465+ type . ContainingAssembly . Identity . HasPublicKey == objectTypeSymbol . ContainingAssembly . Identity . HasPublicKey &&
466+ type . ContainingAssembly . Identity . PublicKey . SequenceEqual ( objectTypeSymbol . ContainingAssembly . Identity . PublicKey ) ;
464467
465468 private static ComparisonCheckStatus RecognizeComparisonCheck (
466469 IOperation operation ,
@@ -488,7 +491,7 @@ private static ComparisonCheckStatus RecognizeComparisonCheck(
488491 return ComparisonCheckStatus . Unknown ;
489492 }
490493
491- private static void AnalyzeIsTrueOrIsFalseInvocation ( OperationAnalysisContext context , IOperation conditionArgument , bool isTrueInvocation )
494+ private static void AnalyzeIsTrueOrIsFalseInvocation ( OperationAnalysisContext context , IOperation conditionArgument , bool isTrueInvocation , INamedTypeSymbol objectTypeSymbol )
492495 {
493496 RoslynDebug . Assert ( context . Operation is IInvocationOperation , "Expected IInvocationOperation." ) ;
494497
@@ -575,7 +578,7 @@ private static void AnalyzeIsTrueOrIsFalseInvocation(OperationAnalysisContext co
575578 }
576579
577580 // Check for collection method patterns: myCollection.Contains(...)
578- CollectionCheckStatus collectionMethodStatus = RecognizeCollectionMethodCheck ( conditionArgument , out SyntaxNode ? collectionExpr , out SyntaxNode ? itemExpr ) ;
581+ CollectionCheckStatus collectionMethodStatus = RecognizeCollectionMethodCheck ( conditionArgument , objectTypeSymbol , out SyntaxNode ? collectionExpr , out SyntaxNode ? itemExpr ) ;
579582 if ( collectionMethodStatus != CollectionCheckStatus . Unknown )
580583 {
581584 if ( collectionMethodStatus == CollectionCheckStatus . Contains )
@@ -705,7 +708,7 @@ private static void AnalyzeIsTrueOrIsFalseInvocation(OperationAnalysisContext co
705708 }
706709 }
707710
708- private static void AnalyzeAreEqualOrAreNotEqualInvocation ( OperationAnalysisContext context , IOperation expectedArgument , bool isAreEqualInvocation )
711+ private static void AnalyzeAreEqualOrAreNotEqualInvocation ( OperationAnalysisContext context , IOperation expectedArgument , bool isAreEqualInvocation , INamedTypeSymbol objectTypeSymbol )
709712 {
710713 // Check for collection count patterns: collection.Count/Length == 0 or collection.Count/Length == X
711714 if ( isAreEqualInvocation )
@@ -716,6 +719,7 @@ private static void AnalyzeAreEqualOrAreNotEqualInvocation(OperationAnalysisCont
716719 CountCheckStatus countStatus = RecognizeCountCheck (
717720 expectedArgument ,
718721 actualArgumentValue ,
722+ objectTypeSymbol ,
719723 out SyntaxNode ? collectionExpr ,
720724 out _ ,
721725 out _ ) ;
@@ -822,6 +826,7 @@ actualArgumentValue.Type is { } actualType &&
822826 private static CountCheckStatus RecognizeCountCheck (
823827 IOperation expectedArgument ,
824828 IOperation actualArgument ,
829+ INamedTypeSymbol objectTypeSymbol ,
825830 out SyntaxNode ? collectionExpression ,
826831 out SyntaxNode ? countExpression ,
827832 out int countValue )
@@ -833,7 +838,7 @@ expectedArgument.ConstantValue.Value is int expectedValue &&
833838 actualArgument is IPropertyReferenceOperation propertyRef &&
834839 propertyRef . Property . Name is "Count" or "Length" &&
835840 propertyRef . Instance ? . Type is not null &&
836- IsCollectionType ( propertyRef . Instance . Type ) )
841+ IsBCLCollectionType ( propertyRef . Property . ContainingType , objectTypeSymbol ) )
837842 {
838843 collectionExpression = propertyRef . Instance . Syntax ;
839844 countExpression = propertyRef . Syntax ;
@@ -848,7 +853,7 @@ actualArgument.ConstantValue.Value is int actualValue &&
848853 expectedArgument is IPropertyReferenceOperation propertyRef2 &&
849854 propertyRef2 . Property . Name is "Count" or "Length" &&
850855 propertyRef2 . Instance ? . Type is not null &&
851- IsCollectionType ( propertyRef2 . Instance . Type ) )
856+ IsBCLCollectionType ( propertyRef2 . Property . ContainingType , objectTypeSymbol ) )
852857 {
853858 collectionExpression = propertyRef2 . Instance . Syntax ;
854859 countExpression = propertyRef2 . Syntax ;
0 commit comments