Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CA2021 false positive for .Cast<> from generic base class in .NET 9.0 SDK #7357

Open
bgrainger opened this issue Jul 20, 2024 · 7 comments
Open
Labels
Area-Microsoft.CodeAnalysis.NetAnalyzers False_Positive A diagnostic is reported for non-problematic case help wanted The issue is up-for-grabs, and can be claimed by commenting

Comments

@bgrainger
Copy link
Contributor

Analyzer

Diagnostic ID: CA2021: Type 'GenericBase' is incompatible with type 'GenericDerived' and cast attempts will throw InvalidCastException at runtime

Analyzer source

SDK: Version 9.0.100-preview.6.24238.19

❯ dotnet --info
.NET SDK:
 Version:           9.0.100-preview.6.24328.19
 Commit:            ef4c241666
 Workload version:  9.0.100-manifests.bbb3781c
 MSBuild version:   17.11.0-preview-24318-05+4a45d5633

Also tested with NuGet Package: <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0-preview.24318.1">

Describe the bug

NOTE: this may be a duplicate of #7153. However, since that was fixed a few months ago, I would have expected the fix to be in the latest package and latest .NET 9 Preview SDK. Opening this issue just in case it's not truly fixed.

Using OfType<TDerived>() or Cast<TDerived> on a collection of TBase<T> where TDerived : TBase<T> gives a spurious message about a cast that will fail at runtime, even though the cast is actually fine.

Steps To Reproduce

Build the following code with .NET 9 Preview 6:

// works as expected
new List<Base>() { new Derived() }.Cast<Derived>().ToList();

// same code but generic, fails
// CastTest\Program.cs(7,1,7,77): warning CA2021: Type 'GenericBase<int>' is incompatible with type 'GenericDerived' and cast attempts will throw InvalidCastException at runtime (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2021)
new List<GenericBase<int>>() { new GenericDerived() }.Cast<GenericDerived>().ToList();

class Base
{
}

class Derived : Base
{
}

class GenericBase<T>
{
}

class GenericDerived : GenericBase<int>
{
}

Expected behavior

Compiles without warning, as it did in .NET 8.0.

Actual behavior

Issues a spurious CA2021 warning.

@buyaa-n
Copy link
Contributor

buyaa-n commented Aug 23, 2024

NOTE: this may be a duplicate of #7153.

It doesn't seem fixed with #7153 CC @fowl2

@buyaa-n buyaa-n added Area-Microsoft.CodeAnalysis.NetAnalyzers False_Positive A diagnostic is reported for non-problematic case help wanted The issue is up-for-grabs, and can be claimed by commenting labels Aug 23, 2024
@bgrainger
Copy link
Contributor Author

Still a problem with Preview 7:

❯ dotnet --info
.NET SDK:
 Version:           9.0.100-preview.7.24407.12
 Commit:            d672b8a045
 Workload version:  9.0.100-manifests.baed1e37
 MSBuild version:   17.12.0-preview-24374-02+48e81c6f1

@rjgotten
Copy link

Being hit by this with the 9.0.100 RTM SDK, even on a solution targeting .NET 8.

My particular variant is from a generic base class to a generic derived class, i.e.

new Base<int>[] { new() }.OfType<Derived<int>>()

class Base<T> { }
class Derived<T> : Base<T> {}

@CRidge
Copy link

CRidge commented Nov 18, 2024

We're seeing something similar when compiling our dotnet 8 solution using dotnet 9.0.100 SDK:

dotnet --version
9.0.100

We have a repo pattern where the repo fetches data from Entity Framework, and ensures the returned value is the correct type - but the signature of the repo method returns a base class:

Task<IEnumerable<BaseEntity>> GetEntitiesAsync([predicate here]);

When doing a .OfType() on the result, we get a CA2021.

We also get CA2021 when we call .Cast<BaseEntity>() an IEnumerable<RealEntity>. What's interesting, is that RealEntity extends a class - let's call it LocalBaseEntity, which is defined in the same project and extends BaseEntity:

internal class RealEntity : LocalBaseEntity { ... }
internal class LocalBaseEntity : BaseEntity { ... }

BaseEntity is defined in a custom NuGet package we have.

Calling Cast<LocalBaseEntity>() works just fine, even if Cast<BaseEntity>() does not. Could it be an issue with a NuGet package being built with dotnet 8 SDK, but the consumer using dotnet 9 SDK?

The whole thing works just fine when building with dotnet 8 SDK.

@rjgotten
Copy link

rjgotten commented Nov 18, 2024

Could it be an issue with a NuGet package being built with dotnet 8 SDK, but the consumer using dotnet 9 SDK?

The issue is more than likely that the analyzer ships as part of the SDK.
But what code exactly shipped as part of the 9.0.100 stable SDK is basically impossible to find out for outsiders, because the
https://github.com/dotnet/roslyn-analyzers repository does not properly tag releases. They are quite a disorganized mess. Needs some cleaning and structuring.

@risperdal
Copy link

Same here

dotnet --info
.NET SDK:
 Version:           9.0.100
 Commit:            59db016f11
 Workload version:  9.0.100-manifests.c6f19616
 MSBuild version:   17.12.7+5b8665660

@0xced
Copy link

0xced commented Nov 25, 2024

Hit by this too with the .NET 9.0.100 SDK. It's a shame was not fixed before the public release.

Real example that happened with MudDataGrid.RenderedColumns which is a List<Column<T>>

_dataGrid?.RenderedColumns.OfType<PropertyColumn<Employee, string>>();

The code works exactly as expected but triggers CA2021.

@bgrainger bgrainger changed the title CA2021 false positive for .Cast<> from generic base class in .NET 9.0 Preview 6 CA2021 false positive for .Cast<> from generic base class in .NET 9.0 SDK Nov 25, 2024
danpere added a commit to danpere/roslyn-analyzers that referenced this issue Nov 27, 2024
JoeRobich pushed a commit that referenced this issue Dec 2, 2024
JoeRobich added a commit that referenced this issue Dec 3, 2024
Fix for #7357.

#7183 changed the CastWillAlwaysFail(ITypeSymbol castFrom, ITypeSymbol castTo) to work with castFrom.OriginalDefinition/castTo.OriginalDefinition, eliding the generic type information in order to fix issues with interfaces, but that appears to have broken the check for class types. This reverts just the class type case to use the castFrom/castTo types passed in instead of the .OriginalDefinition. I'm not sure I quite understand why that's the only place this matters, but I tried a few variations with structs and interfaces and couldn't get any other false positives.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Microsoft.CodeAnalysis.NetAnalyzers False_Positive A diagnostic is reported for non-problematic case help wanted The issue is up-for-grabs, and can be claimed by commenting
Projects
None yet
Development

No branches or pull requests

6 participants