diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer.cs index bc3dda1329..6770beb450 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer.cs @@ -216,16 +216,40 @@ static bool CastWillAlwaysFail(ITypeSymbol castFrom, ITypeSymbol castTo) return false; } - static bool IsUnconstrainedTypeParameter(ITypeParameterSymbol typeParameterSymbol) - => !typeParameterSymbol.HasValueTypeConstraint - && typeParameterSymbol.ConstraintTypes.IsEmpty; - // because object is a reference type the 'class' reference type constraint - // doesn't actually constrain unless a type is specified too - // not implemented: - // NotNullConstraint - // ConstructorConstraint - // UnmanagedTypeConstraint - // Nullability annotations + static bool CastToTypeParamWillAlwaysFail(ITypeSymbol castFrom, ITypeParameterSymbol castToTypeParam) + { + if (castToTypeParam.HasValueTypeConstraint + && ValueTypeConstraintImpossible(castFrom)) + { + return true; + } + + // because object is a reference type the 'class' reference type constraint + // doesn't actually constrain unless a type is specified too + // not implemented: + // NotNullConstraint + // ConstructorConstraint + // UnmanagedTypeConstraint + // Nullability annotations + + if (castToTypeParam.ConstraintTypes.Any(constraintType => CastWillAlwaysFail(castFrom, constraintType))) + { + return true; + } + + return false; + } + + static bool ValueTypeConstraintImpossible(ITypeSymbol t) + { + if (t.TypeKind == TypeKind.Class) + { + return t.SpecialType is not SpecialType.System_Enum + and not SpecialType.System_ValueType; + } + + return false; + } switch (castFrom.TypeKind, castTo.TypeKind) { @@ -235,42 +259,25 @@ static bool IsUnconstrainedTypeParameter(ITypeParameterSymbol typeParameterSymbo case (TypeKind.TypeParameter, _): var castFromTypeParam = (ITypeParameterSymbol)castFrom; - if (IsUnconstrainedTypeParameter(castFromTypeParam)) - { - return false; - } - if (castFromTypeParam.ConstraintTypes.Any(constraintType => CastWillAlwaysFail(constraintType, castTo))) { return true; } if (castFromTypeParam.HasValueTypeConstraint - && castTo.TypeKind == TypeKind.Class) - { - return true; - } - - return false; - case (_, TypeKind.TypeParameter): - var castToTypeParam = (ITypeParameterSymbol)castTo; - if (IsUnconstrainedTypeParameter(castToTypeParam)) - { - return false; - } - - if (castToTypeParam.ConstraintTypes.Any(constraintType => CastWillAlwaysFail(castFrom, constraintType))) + && ValueTypeConstraintImpossible(castTo)) { return true; } - if (castToTypeParam.HasValueTypeConstraint - && castFrom.TypeKind == TypeKind.Class) + if (castTo.TypeKind == TypeKind.TypeParameter) { - return true; + return CastToTypeParamWillAlwaysFail(castFrom, (ITypeParameterSymbol)castTo); } return false; + case (_, TypeKind.TypeParameter): + return CastToTypeParamWillAlwaysFail(castFrom, (ITypeParameterSymbol)castTo); case (TypeKind.Class, TypeKind.Class): return !castFromParam.DerivesFrom(castToParam) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs index 7e6523ce82..9ee0cd68ad 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzerTests.cs @@ -910,6 +910,82 @@ class GenericDerived : GenericBase await test.RunAsync(); } + [Fact, WorkItem(7031, "https://github.com/dotnet/roslyn-analyzers/issues/7031")] + public async Task GenericConstraints() + { + // ensure runtime behavior is matches + _ = new Enum[] { StringComparison.OrdinalIgnoreCase }.Cast().ToArray(); + _ = new ValueType[] { int.MaxValue }.Cast().ToArray(); + + var test = new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net90, + LanguageVersion = LanguageVersion.Latest, + + TestCode = @" +using System; +using System.Collections.Generic; +using System.Linq; + +public static class Program +{ + public static IEnumerable CastFromEnums(IEnumerable values) where T : struct, Enum + => values.Cast(); + + public static IEnumerable CastFromValueTypes(IEnumerable values) where T : struct + => values.Cast(); + + public static IEnumerable CastToEnums(IEnumerable values) where T : struct, Enum + => values.Cast(); + + public static IEnumerable CastToValueTypes(IEnumerable values) where T : struct + => values.Cast(); + + public static IEnumerable CastValueTypes(IEnumerable values) where T : struct + => values.Cast(); + + public static IEnumerable CastUnconstrainedGeneric(IEnumerable values) + => values.Cast(); + + public static IEnumerable CastGenericUnmanagedToGeneric(IEnumerable values) + where TOut : unmanaged + => values.Cast(); + + public static IEnumerable CastGenericUnmanagedToGenericUnmanagedl(IEnumerable values) + where TIn : unmanaged + where TOut : unmanaged + => values.Cast(); + + public static IEnumerable CastGenericNotNullToGenericNotNull(IEnumerable values) + where TIn : notnull + where TOut : notnull + => values.Cast(); + + public static IEnumerable CastGenericToGeneric(IEnumerable values) + where TIn : Enum + where TOut : Uri + => {|#1:values.Cast()|}; + + public static IEnumerable CastGenericStructToGeneric(IEnumerable values) + where TIn : struct + where TOut : Uri + => {|#2:values.Cast()|}; + + public static IEnumerable CastGenericToGenericStruct(IEnumerable values) + where TIn : Uri + where TOut : struct + => {|#3:values.Cast()|}; +}", + ExpectedDiagnostics = + { + VerifyCS.Diagnostic(castRule).WithLocation(1).WithArguments("TIn", "TOut"), + VerifyCS.Diagnostic(castRule).WithLocation(2).WithArguments("TIn", "TOut"), + VerifyCS.Diagnostic(castRule).WithLocation(3).WithArguments("TIn", "TOut"), + } + }; + await test.RunAsync(); + } + [Fact] public async Task NonGenericCasesVB() {