Skip to content

If a generic class is subscripted, infer that class itself#771

Closed
mthuurne wants to merge 3 commits intopylint-dev:masterfrom
boxingbeetle:master
Closed

If a generic class is subscripted, infer that class itself#771
mthuurne wants to merge 3 commits intopylint-dev:masterfrom
boxingbeetle:master

Conversation

@mthuurne
Copy link
Copy Markdown

@mthuurne mthuurne commented Apr 27, 2020

Description

Ideally, when a generic class is subscripted, the value for the type variable would be remembered and used in later inference. However, just interring the subscripted class itself already solves multiple false positives in pylint, so this seems like a useful first step.

Type of Changes

Type
🐛 Bug fix
✨ New feature
🔨 Refactoring
📜 Docs

Related Issue

Closes pylint-dev/pylint#3131
Closes pylint-dev/pylint#3505

It might also solve pylint-dev/pylint#3129, but I'm having trouble reproducing that issue on Python 3.8 even without this PR applied.

@mthuurne mthuurne force-pushed the master branch 2 times, most recently from a14df8c to 070dae4 Compare April 27, 2020 23:37
@mthuurne
Copy link
Copy Markdown
Author

I just got an InconsistentMroError when checking SoftFab with this PR applied. So please do review the PR, but be warned that it either directly or indirectly breaks pylint.

@mthuurne
Copy link
Copy Markdown
Author

mthuurne commented May 1, 2020

Part of the problem seems to be that one ClassDef instance for typing.Generic is not equal to another ClassDef instance for typing.Generic. This leads to the C3 algorithm not discarding some duplicate bases.

How is this supposed to work? Should ClassDef override __eq__() and compare qname()? Or maybe qname() plus source file and line, to be able to tell apart redefinitions?

Or should there some kind of mechanism that ensures there is a single representative instance for each member of the typing module? Perhaps astroid.MANAGER could be used for this, but it's unclear to me how to properly populate and query its cache.

mthuurne added 3 commits May 1, 2020 02:57
In Python 3.6, 'typing.Generic' is redefined, so if we only check the
first option, we're missing the actual definition.
When no module name is passed, it defaults to the empty string,
which leads to an incorrect qname on the inferrence result.
@NeilGirdhar
Copy link
Copy Markdown
Contributor

might also close pylint-dev/pylint#3684 ?

@NeilGirdhar
Copy link
Copy Markdown
Contributor

NeilGirdhar commented Sep 27, 2020

@PCManticore This pull request would be very helpful. Would you be able to provide some feedback about how you want it implemented?

Copy link
Copy Markdown
Contributor

@PCManticore PCManticore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall the change looks good, but I wonder the following:

  1. Can we define a Generic object in objects that acts as a future placeholder for a more thorough inference? This would be similar to the other objects that we have defined there, like Super, FrozenSet, DictKeys and whatnot. The way it could work would be that when we detect the typing.Generic, we emit instead a Generic instance after the inference. The Generic instance could contain the original type and for all intents and purposes could act as a Instance or ClassDef (I'm not sure what typing.Generic is in Python)
  2. You mentioned some possible errors that break pylint. I'd like to see those reflected in tests over here in astroid, so it would be easier to detect what's broken and potentially fix the problems before shipping this change.
  3. Same here with regards to the MRO issues, it would be great if those could be replicated in tests over here.

"""Infer a typing.X[...] subscript"""
try:
value = next(node.value.infer())
for value in node.value.infer():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why we were not passing the context parameter to value inference. Can you add it here and see if it has any unintended side effects? (e.g. if certain tests start to fail, let's keep it removed)

Comment thread astroid/inference.py
yield util.Uninferable
return None
if isinstance(value, nodes.ClassDef):
if value.is_subtype_of("typing.Generic"):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also pass the context to is_subtype_of? This should prevent potential circular inference that could cause RecursionErrors to pop up.

@Pierre-Sassoulas
Copy link
Copy Markdown
Member

Probably outdated as it aim to fixes three issues that are closed. Seems like #927 is what fixed them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

False positive non-parent-init-called with base inheriting Generic and Protocol False positive no-member on member of generic grandparent class

4 participants