Proposed Rule Name: AvoidInterfaceAsMapKey
Proposed Category: Error Prone
Description: In apex, when a Map uses an interface as key, where equals / hashCode are implemented by an abstract class implementation of the interface, methods on Map such as containsKey do not dispatch to the correct equals/hashCode implementation.
Involves many classes which might make a more accurate rule implementation difficult, easiest would be to prevent usage of interfaces in key, but that would likely be too broad. An ideal implementation would probably be difficult - where an interface is used as key & there is an implementation of that interface that defines equals/abstract.
Code Sample: A minimal reproduction of the apex bug can be seen here: https://github.com/Traction-Rec/map-eq-dispatch-bug/blob/main/force-app/main/default/classes/MapEqualsDispatchBugTest.cls
public interface IKey {
Boolean equals(Object obj);
Integer hashCode();
String toString();
}
public abstract class AbstractKey implements IKey {
protected abstract String getKeyValue();
public Boolean equals(Object obj) {
System.debug('AbstractKey.equals() called - dispatch worked!');
if (obj instanceof AbstractKey) {
return ((AbstractKey) obj).getKeyValue() == this.getKeyValue();
}
return false;
}
public Integer hashCode() {
System.debug('AbstractKey.hashCode() called - dispatch worked!');
String v = getKeyValue();
return v != null ? System.hashCode(v) : 0;
}
public override String toString() {
return getKeyValue();
}
}
public class StringKey extends AbstractKey {
private String value;
public StringKey(String value) {
this.value = value;
}
protected override String getKeyValue() {
return value;
}
}
@IsTest
private class MapEqualsDispatchBugTest {
@IsTest
static void containsKeyWithAbstractMiddle() {
Map<IKey, String> m = new Map<IKey, String>(); // Rule would throw, violation is use of this interface
IKey k1 = new StringKey('hello');
m.put(k1, 'value');
IKey k2 = new StringKey('hello');
System.assertEquals(k1.hashCode(), k2.hashCode());
for (IKey existing : m.keySet()) {
System.assert(k2.equals(existing), 'k2.equals(existing)');
System.assert(existing.equals(k2), 'existing.equals(k2)');
}
System.assert(m.containsKey(k2), 'Hypothesis: containsKey with interface→abstract→concrete (StringKey)'); // This incorrectly returns false
}
}
Possible Properties:
I don't think properties are necessary / useful
Proposed Rule Name: AvoidInterfaceAsMapKey
Proposed Category: Error Prone
Description: In apex, when a
Mapuses an interface as key, where equals / hashCode are implemented by an abstract class implementation of the interface, methods onMapsuch ascontainsKeydo not dispatch to the correct equals/hashCode implementation.Involves many classes which might make a more accurate rule implementation difficult, easiest would be to prevent usage of interfaces in key, but that would likely be too broad. An ideal implementation would probably be difficult - where an interface is used as key & there is an implementation of that interface that defines equals/abstract.
Code Sample: A minimal reproduction of the apex bug can be seen here: https://github.com/Traction-Rec/map-eq-dispatch-bug/blob/main/force-app/main/default/classes/MapEqualsDispatchBugTest.cls
Possible Properties:
I don't think properties are necessary / useful