From de9cac145984662be06e993dc9ae7cbbfa700873 Mon Sep 17 00:00:00 2001 From: James May Date: Thu, 8 Feb 2024 18:37:23 +1100 Subject: [PATCH 1/3] fix CA2021 false positives with interfaces reported in #7183 --- ...stOrOfTypeWithIncompatibleTypesAnalyzer.cs | 41 +++++++++--------- ...fTypeWithIncompatibleTypesAnalyzerTests.cs | 42 ++++++++++++++++++- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer.cs index 48c7b0c134..a1f3ce44e4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer.cs @@ -196,6 +196,9 @@ static bool CastWillAlwaysFail(ITypeSymbol castFrom, ITypeSymbol castTo) return false; } + castFrom = castFrom.OriginalDefinition; + castTo = castTo.OriginalDefinition; + if (castFrom.SpecialType == SpecialType.System_Object || castTo.SpecialType == SpecialType.System_Object) { @@ -220,14 +223,14 @@ static bool IsUnconstrainedTypeParameter(ITypeParameterSymbol typeParameterSymbo // UnmanagedTypeConstraint // Nullability annotations - switch (castFrom.OriginalDefinition.TypeKind, castTo.OriginalDefinition.TypeKind) + switch (castFrom.TypeKind, castTo.TypeKind) { case (TypeKind.Dynamic, _): case (_, TypeKind.Dynamic): return false; case (TypeKind.TypeParameter, _): - var castFromTypeParam = (ITypeParameterSymbol)castFrom.OriginalDefinition; + var castFromTypeParam = (ITypeParameterSymbol)castFrom; if (IsUnconstrainedTypeParameter(castFromTypeParam)) { return false; @@ -246,7 +249,7 @@ static bool IsUnconstrainedTypeParameter(ITypeParameterSymbol typeParameterSymbo return false; case (_, TypeKind.TypeParameter): - var castToTypeParam = (ITypeParameterSymbol)castTo.OriginalDefinition; + var castToTypeParam = (ITypeParameterSymbol)castTo; if (IsUnconstrainedTypeParameter(castToTypeParam)) { return false; @@ -270,32 +273,32 @@ static bool IsUnconstrainedTypeParameter(ITypeParameterSymbol typeParameterSymbo && !castTo.DerivesFrom(castFrom); case (TypeKind.Interface, TypeKind.Class): - return castTo.IsSealed && !castTo.AllInterfaces.Contains(castFrom); + return castTo.IsSealed && !castTo.DerivesFrom(castFrom); case (TypeKind.Class, TypeKind.Interface): - return castFrom.IsSealed && !castFrom.AllInterfaces.Contains(castTo); + return castFrom.IsSealed && !castFrom.DerivesFrom(castTo); case (TypeKind.Interface, TypeKind.Struct): - return !castTo.AllInterfaces.Contains(castFrom); + return !castTo.DerivesFrom(castFrom); case (TypeKind.Struct, TypeKind.Interface): - return !castFrom.AllInterfaces.Contains(castTo); + return !castFrom.DerivesFrom(castTo); case (TypeKind.Class, TypeKind.Enum): - return castFrom.OriginalDefinition.SpecialType is not SpecialType.System_Enum - and not SpecialType.System_ValueType; + return castFrom.SpecialType is not SpecialType.System_Enum + and not SpecialType.System_ValueType; case (TypeKind.Enum, TypeKind.Class): - return castTo.OriginalDefinition.SpecialType is not SpecialType.System_Enum - and not SpecialType.System_ValueType; + return castTo.SpecialType is not SpecialType.System_Enum + and not SpecialType.System_ValueType; case (TypeKind.Struct, TypeKind.Enum) - when castTo.OriginalDefinition is INamedTypeSymbol toEnum: + when castTo is INamedTypeSymbol toEnum: return !castFrom.Equals(toEnum.EnumUnderlyingType); case (TypeKind.Enum, TypeKind.Struct) - when castFrom.OriginalDefinition is INamedTypeSymbol fromEnum: + when castFrom is INamedTypeSymbol fromEnum: return !fromEnum.EnumUnderlyingType!.Equals(castTo); case (TypeKind.Enum, TypeKind.Enum) - when castFrom.OriginalDefinition is INamedTypeSymbol fromEnum - && castTo.OriginalDefinition is INamedTypeSymbol toEnum: + when castFrom is INamedTypeSymbol fromEnum + && castTo is INamedTypeSymbol toEnum: return !fromEnum.EnumUnderlyingType!.Equals(toEnum.EnumUnderlyingType); // this is too conservative @@ -310,14 +313,14 @@ when castFrom is IArrayTypeSymbol fromArray || CastWillAlwaysFail(fromArray.ElementType, toArray.ElementType); case (TypeKind.Array, TypeKind.Class): - return castTo.OriginalDefinition.SpecialType != SpecialType.System_Array; + return castTo.SpecialType != SpecialType.System_Array; case (TypeKind.Class, TypeKind.Array): - return castFrom.OriginalDefinition.SpecialType != SpecialType.System_Array; + return castFrom.SpecialType != SpecialType.System_Array; case (TypeKind.Class, TypeKind.Struct): - return castFrom.OriginalDefinition.SpecialType != SpecialType.System_ValueType; + return castFrom.SpecialType != SpecialType.System_ValueType; case (TypeKind.Struct, TypeKind.Class): - return castTo.OriginalDefinition.SpecialType != SpecialType.System_ValueType; + return castTo.SpecialType != SpecialType.System_ValueType; case (_, TypeKind.Enum): case (TypeKind.Enum, _): diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs index b0d72834e6..b6fdc38e82 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System; using System.Collections.Generic; @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< Microsoft.NetCore.Analyzers.Runtime.DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer, @@ -825,6 +826,45 @@ void Mstruct() where TStruct : struct }.RunAsync(); } + [Fact] + public async Task GenericRecordsAndInterfaces() + { + var test = new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net70, + LanguageVersion = LanguageVersion.CSharp10, + TestCode = @" +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +public static class Program +{ + public static void Main() + { + var nodeChanges = new List> { new DataNodeUpdate(new DataNode(0, 0)) }; + + //warning CA2021: This call will always result in an empty sequence because type 'INodeUpdate' is incompatible with type 'DataNodeUpdate' + var nodeChangesFiltered = nodeChanges.OfType(); + + Debug.Assert(nodeChangesFiltered.Count() == 1); + } +} + +public abstract record class GraphNode(long Id); +public sealed record class DataNode(long Id, long Value) : GraphNode(Id); + +public interface INodeUpdate +{ + T Updated { get; } +} + +public abstract record class NodeUpdate(T Updated) : INodeUpdate where T : GraphNode; +public sealed record class DataNodeUpdate(DataNode Updated) : NodeUpdate(Updated);" + }; + await test.RunAsync(); + } + [Fact] public async Task NonGenericCasesVB() { From 345445b893f97ceb301343419360482a6efcd6f6 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Thu, 15 Feb 2024 14:35:48 -0800 Subject: [PATCH 2/3] Update src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs --- ...lEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs index b6fdc38e82..bd0bb3348b 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs @@ -826,7 +826,7 @@ void Mstruct() where TStruct : struct }.RunAsync(); } - [Fact] + [Fact, WorkItem(7153, "https://github.com/dotnet/roslyn-analyzers/issues/7153")] public async Task GenericRecordsAndInterfaces() { var test = new VerifyCS.Test From b87e72e139a86a6a6aef155fd817a512d65d98c5 Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Thu, 15 Feb 2024 15:13:46 -0800 Subject: [PATCH 3/3] Update src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs --- ...llEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs index bd0bb3348b..271b683d3e 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; +using Test.Utilities; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< Microsoft.NetCore.Analyzers.Runtime.DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer,