Skip to content

Roslyn doesn't consider quasi-implemenations of IEqualityComparer.Equals and IEqutable.Equals as well known members in nullability analysis #40577

@TessenR

Description

@TessenR

Version Used:

Branch master (23 Dec 2019)
Latest commit 8614121 by Charles Stoner:
Do not include binding cache in BoundLambda (#40534)

Steps to Reproduce:

Compile the following code:

#nullable enable
using System.Collections.Generic;

class Base
{
    public bool Equals(string? x, string? y) => true;
    public int GetHashCode(string x) => 0;
}

class Quasi : Base, IEqualityComparer<string>
{
   void M(string? s)
   {
     if (Equals(s, null)) return;
     
     s.ToString();
   }
}

Expected Behavior:
No warnings. s is compared to null via IEqualityComparer<string>.Equals and the method does not dereference it in such cases.

Actual Behavior:
warning CS8602: Dereference of a possibly null reference. is reported for s.ToString()

Notes
If you provide an implementation of Equals in Quasi instead of quasi-implementing it via the Equals method in Base the warning goes away so it seems that Roslyn just doesn't check quasi-implementations when checking for well-known equality methods in NullableWalker.LearnFromEqualsMethod.isWellKnownEqualityMethodOrImplementation which seems to be a bug.

i.e. there's no such warning in the following code:

#nullable enable
using System.Collections.Generic;

class Base
{
    
    public int GetHashCode(string x) => 0;
}

class Quasi : Base, IEqualityComparer<string>
{
   public bool Equals(string? x, string? y) => true;
   void M(string? s)
   {
     if (Equals(s, null)) return;
     
     s.ToString();
   }
}

As mentioned above the same problem exists with other well-known interfaces' metods e.g. IEqutable.Equals. The following code snippet does not produce any warnings for dereferencing s despite it being explicitly compared to null. It will report the warning correctly if you move the Equals(Quasi? x) method from Base to Quasi:

#nullable enable
using System;

class Base
{
  public bool Equals(Quasi? y) => true;
}

class Quasi : Base, IEquatable<Quasi>
{

   void M(Quasi s)
   {
     if (s.Equals(null))
     {
       s.ToString();
     }
   }
}

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions