Repository: pylint-dev/pylint (Python)

Issue:
False positive E1143 unhashable-member with class type as key
### Bug description

A class type used as key for a dict is valid, but is incorrectly reported as unhashable. 

See Expected Behavior for example.

### Configuration

https://github.com/crim-ca/weaver/blob/master/.pylintrc

### Command used

```shell
pylint \
	--load-plugins pylint_quotes \
	--rcfile="$(APP_ROOT)/.pylintrc" \
	--reports y \
	"$(APP_ROOT)/weaver" "$(APP_ROOT)/tests"
```


### Pylint output

```shell
************* Module weaver.store.mongodb
weaver/store/mongodb.py:426:12: E1143: 'Process' is unhashable and can't be used as a key in a dict (unhashable-member)
weaver/store/mongodb.py:432:48: E1143: 'Process' is unhashable and can't be used as a key in a dict (unhashable-member)
```


### Expected behavior

The specific class I used is defined here: https://github.com/crim-ca/weaver/blob/master/weaver/datatype.py#L1778

It essentially derive from a `dict` base, and creates a class type. 
When calling the below code, everything works as expected, but pylint still thinks it is not hashable. 

```python 
from weaver.datatype import Process
d = {Process: "test"}
d[Process]  # outputs 'test' 
```

### Pylint version

```shell
❯ pylint --version
pylint 2.15.3
astroid 2.12.10
Python 3.7.9 (default, Aug 31 2020, 12:42:55) 
[GCC 7.3.0]
```


### OS / Environment

Ubuntu 20.04

### Additional dependencies

aenum==3.1.11
aiohttp==3.8.1
aiosignal==1.2.0
alabaster==0.7.12
alembic==1.4.3
amqp==5.1.0
appdirs==1.4.4
argcomplete==2.0.0
asciitree==0.3.3
asn1crypto==1.5.1
astor==0.8.1
astroid==2.12.10
async-timeout==4.0.2
asynctest==0.13.0
attrs==20.3.0
Authomatic @ https://github.com/fmigneault/authomatic/archive/httplib-port.zip
autopep8==1.5.7
aws-sam-translator==1.26.0
aws-xray-sdk==2.6.0
awswrangler==2.16.1
Babel==2.8.0
backoff==2.1.2
bagit==1.8.1
bandit==1.6.2
bcrypt==3.2.2
Beaker==1.11.0
beautifulsoup4==4.9.1
billiard==3.6.4.0
boto==2.49.0
boto3==1.21.11
botocore==1.24.11
bump2version==1.0.0
CacheControl==0.11.7
cached-property==1.5.2
celery==5.2.2
certifi==2021.5.30
cffi==1.15.0
cfn-lint==0.35.1
Chameleon==3.10.1
chardet==4.0.0
charset-normalizer==2.0.6
click==8.1.0
click-didyoumean==0.3.0
click-plugins==1.1.1
click-repl==0.2.0
cligj==0.7.2
cloud-sptheme==1.10.1.post20200504175005
codacy-coverage==1.3.11
colander==1.8.3
coloredlogs==15.0
configparser==4.0.2
cornice==6.0.1
cornice-swagger @ git+https://github.com/fmigneault/cornice.ext.swagger.git@70eb7022d9ecde48aaae951ac606f23d5a71b84f
coverage==5.2.1
cryptography==36.0.1
cwltool==3.0.20200324120055
decorator==4.4.2
defusedxml==0.7.1
dicttoxml==1.7.4
dill==0.3.5.1
distlib==0.3.1
dnspython==1.16.0
doc8==0.11.2
docformatter==1.5.0
docker==5.0.3
docutils==0.17.1
dparse==0.5.1
drmaa==0.7.9
duration==1.1.1
ecdsa==0.14.1
esgf-compute-api @ git+https://github.com/ESGF/esgf-compute-api.git@8faf829abb3ba0434715c4db44440cfb9c71b55f
et-xmlfile==1.1.0
fasteners==0.17.3
filelock==3.0.12
Fiona==1.8.21
flake8==3.8.3
flufl.enum==4.1.1
flynt==0.76
frozenlist==1.3.1
future==0.18.2
geotiff==0.2.4
gitdb==4.0.5
GitPython==3.1.8
gremlinpython==3.6.1
gunicorn==20.1.0
humanfriendly==9.1
humanize==3.3.0
hupper==1.10.2
idna==3.2
imagesize==1.2.0
importlib-metadata==4.11.3
importlib-resources==5.4.0
iniconfig==1.0.1
iso8601==0.1.14
isodate==0.6.0
isort==4.3.21
Jinja2==3.0.3
jmespath==0.10.0
json2xml==3.16.0
jsondiff==1.2.0
jsonpatch==1.26
jsonpath-ng==1.5.3
jsonpickle==1.4.1
jsonpointer==2.0
jsonschema==4.4.0
junit-xml==1.9
kombu==5.2.4
lazy-object-proxy @ file:///tmp/build/80754af9/lazy-object-proxy_1616529086024/work
lockfile==0.12.2
lxml==4.8.0
-e git+https://github.com/ouranosinc/magpie@6595ece40ce194ff37cc2c9c0ef42fef7f6f9efb#egg=magpie
Mako==1.1.6
MarkupSafe==2.1.1
mccabe==0.6.1
memory-profiler==0.60.0
mistune==0.8.4
mkl-fft==1.3.1
mkl-random @ file:///tmp/build/80754af9/mkl_random_1626179032232/work
mkl-service==2.4.0
mock==3.0.5
more-itertools==5.0.0
moto==3.1.1
multidict==6.0.2
munch==2.5.0
mypy-extensions==0.4.3
nest-asyncio==1.5.5
networkx==2.5
numcodecs==0.9.1
numpy @ file:///opt/conda/conda-bld/numpy_and_numpy_base_1653915516269/work
oauthlib==3.2.0
openpyxl==3.0.10
opensearch-py==1.1.0
OWSLib==0.27.2
packaging==20.4
paginate==0.5.6
paginate-sqlalchemy==0.3.1
pandas==1.3.5
passlib==1.7.4
Paste==3.4.4
PasteDeploy==2.1.1
pathlib2==2.3.5
pbr==5.5.0
pg8000==1.29.1
Pint==0.18
pipdeptree==2.2.1
plaster==1.0
plaster-pastedeploy==0.7
platformdirs==2.5.1
pluggy==0.13.1
ply==3.11
progressbar2==4.0.0
prompt-toolkit==3.0.28
prov==1.5.1
psutil==5.8.0
psycopg2-binary==2.9.3
py @ file:///tmp/build/80754af9/py_1593446248552/work
pyarrow==7.0.0
pyasn1==0.4.8
pycodestyle==2.6.0
pycparser==2.20
pydocstyle==6.1.1
pydot==1.4.2
pyflakes==2.2.0
Pygments==2.6.1
pylint==2.15.3
pylint-quotes==0.2.1
pymongo==3.12.0
PyMySQL==1.0.2
pyOpenSSL==20.0.1
pyparsing==2.4.7
pyproj==3.2.1
pyramid==1.10.8
pyramid-beaker==0.8
pyramid-celery==4.0.0
pyramid-chameleon==0.3
pyramid-debugtoolbar==4.9
pyramid-mako==1.1.0
pyramid-retry==2.1.1
pyramid-rewrite==0.2
pyramid-rpc==0.8
pyramid-storage==1.0.0
pyramid-tm==2.4
pyramid-twitcher==0.5.3
pyrsistent==0.17.3
pystac==1.4.0
pystac-client==0.3.3
pytest==6.1.2
python-dateutil==2.8.2
python-dotenv==0.20.0
python-editor==1.0.4
python-jose==3.2.0
python-utils==3.3.3
python3-openid==3.2.0
pytz==2021.3
pywps==4.5.1
PyYAML==6.0
rdflib==4.2.2
rdflib-jsonld==0.5.0
redshift-connector==2.0.908
repoze.lru==0.7
requests==2.26.0
requests-aws4auth==1.1.2
requests-file==1.5.1
-e git+ssh://git@github.com/Ouranosinc/requests-magpie.git@b26ed3c2cee4395c1bd745c7e102a442ba2199ae#egg=requests_magpie
requests-oauthlib==1.3.0
requests-toolbelt==0.9.1
responses==0.20.0
restructuredtext-lint==1.3.1
rsa==4.6
ruamel.yaml==0.16.5
ruamel.yaml.clib==0.2.2
s3transfer==0.5.2
safety==1.10.3
schema-salad==5.0.20200416112825
scramp==1.4.1
Shapely @ file:///home/conda/feedstock_root/build_artifacts/shapely_1660800422489/work
shellescape==3.4.1
simplejson==3.17.6
six @ file:///tmp/build/80754af9/six_1623709665295/work
smmap==3.0.4
snowballstemmer==2.0.0
soupsieve==2.0.1
Sphinx==4.4.0
sphinx-argparse==0.3.1
sphinx-autoapi==1.7.0
sphinx-autodoc-typehints==1.12.0
sphinx-paramlinks==0.4.2
sphinx-prompt==1.5.0
sphinx-rtd-theme==1.0.0
Sphinx-Substitution-Extensions==2020.9.30.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-redoc==1.6.0
sphinxcontrib-serializinghtml==1.1.5
SQLAlchemy==1.3.22
SQLAlchemy-Utils==0.37.9
sshpubkeys==3.1.0
stevedore==3.2.1
stopit==1.1.2
strict-rfc3339==0.7
threddsclient==0.4.2
tifffile==2021.7.2
toml @ file:///tmp/build/80754af9/toml_1592853716807/work
tomli==2.0.1
tomlkit==0.11.4
tox==3.22.0
transaction==3.0.1
translationstring==1.4
typed-ast @ file:///tmp/build/80754af9/typed-ast_1624953380449/work
typing-extensions @ file:///tmp/build/80754af9/typing_extensions_1631814937681/work
Unidecode==1.1.1
untokenize==0.1.1
urllib3==1.26.7
urlmatch==1.0.1
venusian==3.0.0
vine==5.0.0
virtualenv==20.4.2
waitress==1.4.4
wcwidth==0.2.5
-e git+https://github.com/crim-ca/weaver@860fae706b2b1c74beb500a610be0b2a4ad78d0b#egg=weaver
WebOb==1.8.7
websocket-client==0.57.0
WebTest==2.0.35
Werkzeug==2.2.2
wrapt==1.12.1
WSGIProxy==0.2.2
WSGIProxy2==0.4.6
xmltodict==0.12.0
yarl==1.8.1
ytools==1.0.0
zarr==2.11.1
ziggurat-foundations==0.8.4
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work
zope.deprecation==4.4.0
zope.interface==4.7.2
zope.sqlalchemy==1.6



Discussion:
Thank you for for the report @fmigneault!

I can reproduce using a simplified example which is possibly the same root-cause:
```python
class Tomato(dict):
    ...

{Tomato: 1}
```



Patch:
diff --git a/doc/whatsnew/fragments/7501.false_positive b/doc/whatsnew/fragments/7501.false_positive
new file mode 100644
index 0000000000..0c2d33a075
--- /dev/null
+++ b/doc/whatsnew/fragments/7501.false_positive
@@ -0,0 +1,3 @@
+Fix false positive for ``unhashable-member`` when subclassing ``dict`` and using the subclass as a dictionary key.
+
+Closes #7501
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index ca3d0e362e..0e99b558a9 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -2012,7 +2012,7 @@ def is_hashable(node: nodes.NodeNG) -> bool:
     """
     try:
         for inferred in node.infer():
-            if inferred is astroid.Uninferable:
+            if inferred is astroid.Uninferable or isinstance(inferred, nodes.ClassDef):
                 return True
             if not hasattr(inferred, "igetattr"):
                 return True


Test patch:
diff --git a/tests/functional/u/unhashable_member.py b/tests/functional/u/unhashable_member.py
index a9100b9058..c788668c47 100644
--- a/tests/functional/u/unhashable_member.py
+++ b/tests/functional/u/unhashable_member.py
@@ -22,3 +22,9 @@ class Unhashable:
 {"tomato": "tomahto"}
 {dict: {}}
 {lambda x: x: "tomato"}  # pylint: disable=unnecessary-lambda
+
+
+class FromDict(dict):
+    ...
+
+{FromDict: 1}