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

Add inference tips for dataclass attributes #1126

Merged

Conversation

david-yz-liu
Copy link
Contributor

Steps

  • For new features or bug fixes, add a ChangeLog entry describing what your PR does.
  • Write a good description on what the PR does.

Description

I'm a little sleepy 😴 so I'm going to write up a short version of the description and elaborate later (perhaps upon request only). The short version is I've added two new inference tips related to dataclasses, in astroid/brain/brain_dataclasses.py:

  1. infer_dataclass_field_call, for handling calls to field to infer the default value they actually evaluate to.

  2. infer_dataclass_attribute, for handling inference of dataclass instance attributes. Note: the previous transform modified the dataclass ClassDef node's locals dict:

    https://github.com/PyCQA/astroid/blob/4704ca75539c085cdd0476a8d79771a7d2b8740d/astroid/brain/brain_dataclasses.py#L58-L64

    I thought that was a bit too extreme, as it removed possible inferences that could be done for class attributes. So I removed the update to locals, and so the class variable inference "just works", without using this inference tip. See the tests I added for what I mean.

    ⚠️ In order to get the inference tip working for Unknown nodes, I had to make a few changes to the underlying node infrastructure, namely Unknown.infer and AstroidManager.visit. I'm pretty sure both of these changes are fine, but please let me know if that's an issue.

Using type annotations (?)

For the instance attribute inference, I used the type annotation to provide an alternative to the default value. E.g., x: int = 10 gets two inference results, one for Const(10) and one for Instance of type int. My sense looking at the issues for astroid/pylint is that using type annotations for inference is a possibility but hasn't really been explored. So this PR has one small way foray into using type annotations for inference, but let me know if you want to avoid that. I could yield an Uninferable instead.

Other changes

In addition to these two changes, I updated the existing inference tip (dataclass_transform) to be more robust in checking for dataclass usage (including renaming of the dataclass import), and added basic support for InitVar as well.

Also, Assign nodes shouldn't be included, only AnnAssign nodes. For dataclasses, the "magic" only occurs for class variables with type annotations.

Testing

As usual, I've tried to communicate various intentions through some tests. See also related pylint issues below.

Type of Changes

Type
🐛 Bug fix

Related Issue

Closes pylint-dev/pylint#2600
Closes pylint-dev/pylint#2698
Closes pylint-dev/pylint#3405
Closes pylint-dev/pylint#3794

I've written "closes" even though I couldn't reproduce some of them, but I was going a bit quickly and I'd appreciate a second look.

For the field-specific ones, the commit (3e4eac7) that introduced the transform should have fixed the false positive no-member complaints because Unknown infers to Uninferable, which I think causes pylint to not emit no-member.

However, with the change in this PR, a more specific type is emitted, meaning that the false positives described in the issues should not occur, but there should be fewer false negatives too. E.g.,

from typing import Dict
from dataclasses import dataclass, field

@dataclass
class TestClass:
	foo: str
	bar: str
	baz_dict: Dict[str, str] = field(default_factory=dict)

	def some_func(self) -> None:
		for key, value in self.baz_dict.itemss():  # [no-member] typo here, "itemss" should be "items"
			print(key)
			print(value)

So IMO it'd be good to add regression tests for these issues, even if they aren't reproducible on main. Let me know if you want me to do it.

Copy link
Member

@Pierre-Sassoulas Pierre-Sassoulas left a comment

Choose a reason for hiding this comment

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

Thanks for the great description :D I wonder what it would be like if you were awake and it was much detailed ;)

I looked at the failing tests and it seems like what is failing is failing because it's now better than before (better inference ?). I wonder if the name node should be "Unknown" though.

Regarding the regression tests in pylint, let's add them on top of pylint-dev/pylint#4816, you should have the right to do it but if you don't or if you don't have the time, I'll do it :)

@cdce8p
Copy link
Member

cdce8p commented Aug 15, 2021

Using type annotations for inference

I'm still a bit hesitant about that. As python is a dynamic language, nothing really prevents anyone to assign anything they want. Furthermore, not all types can correctly be inferred currently. That especially applies to TypeVars and Generic types, but there might be others as well.

@david-yz-liu
Copy link
Contributor Author

@Pierre-Sassoulas thanks, I fixed up the tests. Version 3.7 needed a different #@ marker to access the ClassDef node with a @dataclass decorator, and inferring ClassVar didn't work on Python 3.8. I can add some tests to the Pylint branch in a bit.

@cdce8p I think those are totally fair points. Personally I think it's still worthwhile to try to infer at least some basic types, but deciding this can easily be deferred to later. For now, I can change this part of the code to just yield Uninferable, if that works for you? I think this is the same inference as when inferring a function parameter with a default value: both the default value and Uninferable are yielded.

@david-yz-liu
Copy link
Contributor Author

P.S. sorry about the style commits, I hadn't set up the pre-commit hooks in this repo. I can rebase and clean up once the branch is finalized.

@Pierre-Sassoulas
Copy link
Member

I'm still a bit hesitant about that. As python is a dynamic language, nothing really prevents anyone to assign anything they want.

I'm more and more convinced that we must trust what the user is using in the typing. In conjunction with mypy that will tell the user about problem with it's own typing it should make pylint a lot more efficient. There's a discussion about this subject here based on a comment by @PCManticore: pylint-dev/pylint#4813

david-yz-liu added a commit to david-yz-liu/pylint that referenced this pull request Aug 15, 2021
@david-yz-liu
Copy link
Contributor Author

@Pierre-Sassoulas I added tests the branch but I didn't have write access to your repository, but I pushed to my own branch: https://github.com/david-yz-liu/pylint/tree/regression-test-533-2224-2626. I wouldn't think I have edit permissions on MRs to pylint, but maybe I just messed something up, please let me know.

The tests I added are all consistent with yielding Uninferable for instance attributes, as I suggested in my previous comment. But if you guys end up going with the proposed change as is, I'll add a couple more "false negative" tests to illustrate the stronger inference. I'll also add some tests for simple Generics like List[str], I neglected to do so in my initial PR.

Let me know which direction on the inference you'd like to go with, either way is good with me 😄

Pierre-Sassoulas pushed a commit to Pierre-Sassoulas/pylint that referenced this pull request Aug 15, 2021
@Pierre-Sassoulas
Copy link
Member

Hey @david-yz-liu thank you for creating all those tests in pylint. Maybe you had to be a "maintener" in order to modify the branch and "contributor" is not enough, my bad.

Regarding this change I think the way it's done right now is fine. Using typing when the inference fails us is something that we've already done in another MR if I remember correctly. I think using typing all the time even before inference for performance purpose needs to be discussed between maintainers but this is not what is done here. What do you think @hippo91 , @cdce8p ?

@cdce8p
Copy link
Member

cdce8p commented Aug 16, 2021

@Pierre-Sassoulas I understand that you would like to use typing for inference. As mentioned earlier, I'm still not convinced that's the way to go, but then again inference is not really a topic of expertise for me. Let's put it differently, as long as it doesn't cause major regressions or other unforeseen issues, go ahead with it.

@Pierre-Sassoulas Pierre-Sassoulas merged commit bd8818d into pylint-dev:main Aug 16, 2021
Pierre-Sassoulas pushed a commit to Pierre-Sassoulas/pylint that referenced this pull request Aug 16, 2021
@cdce8p
Copy link
Member

cdce8p commented Aug 16, 2021

I haven't been able to narrow it down just yet, but this change causes a regression for Home-Assistant

************* Module homeassistant.components.melcloud.sensor
Error: homeassistant/components/melcloud/sensor.py:118:11: E1102: description.enabled is not callable (not-callable)
Error: homeassistant/components/melcloud/sensor.py:123:11: E1102: description.enabled is not callable (not-callable)
Error: homeassistant/components/melcloud/sensor.py:131:15: E1102: description.enabled is not callable (not-callable)
************* Module homeassistant.components.influxdb.sensor
Error: homeassistant/components/influxdb/sensor.py:175:61: E1102: influx.close is not callable (not-callable)
************* Module homeassistant.components.influxdb
Error: homeassistant/components/influxdb/__init__.py:489:8: E1102: influx.close is not callable (not-callable)

https://github.com/cdce8p/ha-core/tree/f684e4df349f7b95da473a1003f708ac67284848

@cdce8p
Copy link
Member

cdce8p commented Aug 16, 2021

Opened #1129 with a short code snipped that is enough to reproduce the issue.

clrpackages pushed a commit to clearlinux-pkgs/pylint that referenced this pull request Aug 24, 2021
…10.0

Andreas Finkler (3):
      Create common ``Printer`` base class for ``pyreverse`` and improve typing. (#4731)
      ``pyreverse``: add PlantUML output (#4846)
      ``pyreverse``: Add option for colored output (#4850)

Daniël van Noord (9):
      Add unspecified-encoding checker #3826 (#4753)
      Remove classes.dot after testing #4762
      Add new checkers ``use-list-literal`` and ``use-dict-literal`` (#4769)
      Add ``forgotten-debug-statement`` checker (#4771)
      Fix false positives for ``superfluous-parens`` (#4784)
      Refactor ``--list-msgs`` & ``--list-msgs-enabled`` (#4793)
      Add ``format-string-without-interpolation`` checker (#4794)
      Add ``disable-next`` option (#4797)
      Add ``redundant-u-string-prefix`` checker (#4804)

David Liu (6):
      Fix false negative for used-before-assignment (ExceptHandler) (#4791)
      Fix bugs in W0640 cell-var-from-loop checker (#4827)
      Additional tests for pylint-dev/astroid#1126
      Improve invalid-metaclass error message for Instances
      Update test requirements to astroid 2.7.1
      Add typecheck tests for dataclass attributes

DudeNr33 (14):
      Move logic for constructing the label from ``DiagramWriter`` into the ``Printer`` classes.
      Refactor to reduce indentation and pull all "label building" logic together
      Refactor to reduce indentation
      Remove unnecessary specializations of ``DiagramWriter``
      Move tests for ``pyreverse`` into a own subdirectory.
      Rename test files to follow the same conventions as other test modules
      Extract tests for ``pylint.pyreverse.utils`` into own test module.
      Extract commonly used test helpers into ``conftest.py`` and a testutils component.
      Add tests for ``Printer`` classes which are not covered yet implicitly through ``test_writer``
      Use ``default_factory`` for mutable attribute.
      Use custom class, as ``dataclasses`` was only introduced in Python 3.7.
      Update license link to point to main, not master
      Convert ``get_project`` to a fixture instead of implementing it under ``pylint.testutils.pyreverse``.
      Suppress ``consider-using-with`` on return statements

Eisuke Kawashima (1):
      Use `XDG_CACHE_HOME` for `PYLINTHOME` (#4661)

Julien Palard (1):
      Similar check: Passe min_lines to recombined.

Maksym Humetskyi (2):
      [duplicate-code] Ignore decorators lines by similarities checker when ignore signatures flag enabled (#4840)
      [duplicate-code] Parse functions and class methods recursively when gathering signature lines (#4858)

Marc Mueller (22):
      Omit no-member error if guarded behind if stmt (#4764)
      Fix style issues Changelog + What's New
      Extended consider-using-tuple check to cover 'in' comparisons (#4768)
      Remove empty line
      Fix false-positive used-before-assignment with := in Return node
      Revert "Extended consider-using-tuple check to cover 'in' comparisons (#4768)"
      Keep bugfix
      Refactor visit_comprehension
      Add new check - use-sequence-for-iteration
      Fix existing tests
      Improvements consider-using-tuple (#4838)
      Add pylintrc config for typing extension (#4843)
      Allow true / false in pylintrc (#4844)
      Use alias for astroid.nodes 01 (#4855)
      Use alias for astroid.nodes 02 (#4863)
      Use alias for astroid.nodes 03 (#4866)
      Use alias for astroid.nodes 04 (#4869)
      Refactor existing code - code_style extension (#4872)
      Add cached infer_all helper function
      Add InferenceContext argument
      Increase cache size
      Revert cache size increase

Mark Byrne (2):
      Handle has-a relationships for type-hinted arguments in class diagrams (#4745)
      pyreverse - Handle a regression with typehints of type astroid.Subscript (#4859)

Michal Vasilek (1):
      Fix IsADirectoryError in tests/lint/unittest_lint (#4781)

Nick Drozd (4):
      Enable some Pylint extensions (#4842)
      Add extension check against use of while loops (#4860)
      Change some if/assign blocks to if-expressions
      Add consider-ternary-expression extension

PaaEl (1):
      Update option_manager_mixin.py - issue 3839 (#4812)

Pierre Sassoulas (26):
      Move back to a dev version following 2.9.6 release
      Add modify_sys_path to __all__ in pylint API
      Add typing to PyReverse and fix an unused-argument
      Add documentation for useless-suppression
      Do not test unsupported item assignment for python 3.10
      Upgrade astroid to 2.6.6
      Add regression tests for issue #3711
      Add regression tests for astroid issue 1111
      Documentation for what blocks are and FAQ clean up (#4800)
      Fix the link to the discord server in the doc (#4830)
      Add a regression test for #4823
      Permit to lint to the end in case of crash on a file (#4810)
      Simplification in error handling inside Pylinter (#4834)
      Add a regression test for #4837 (#4847)
      Fix crash on relative import beyond top-level package
      Fix functional test no-value-for-parameter when instancing an enum
      Add regression tests for issue with subclassed enums
      Upgrade astroid to 2.7.0
      Limit the dataclasses tests to python 3.7
      Prevent crashing when it's impossible to create the issue template
      ``astroid.const.BUILTINS`` use removed for clarity and so astroid can also removes it later (#4868)
      Add a test for false positive no-member in subclassed dataclasses
      [PYLINT-HOME change] Less spam when using pylint in parallel
      Upgrade astroid to 2.7.2
      Prevent crash in CI environnement if we can't write in pylint's cache
      Bump pylint to 2.10.0, update changelog

Rebecca Turner (1):
      Add ignored-parents option to design checker (#4758)

Yu Shao, Pang (5):
      [unused-private-member] add logic for checking nested functions
      update PR based on comments
      Update pr based on review
      Update pr based on review
      update pr based on review

dependabot[bot] (4):
      Bump sphinx from 4.1.1 to 4.1.2 (#4785)
      Bump isort from 5.9.2 to 5.9.3
      Bump types-toml from 0.1.3 to 0.1.5
      Update pre-commit requirement from ~=2.13 to ~=2.14

hippo91 (1):
      Performance improvment of the Similarity checker (#4565)

kasium (1):
      Fix false positives for invalid-all-format (#4711) (#4829)

pre-commit-ci[bot] (3):
      [pre-commit.ci] auto fixes from pre-commit.com hooks
      [pre-commit.ci] pre-commit autoupdate
      [pre-commit.ci] pre-commit autoupdate

yushao2 (2):
      Fix false-positive of `unused-private-member` with class name (#4782)
      Fix crash for `unused-private-member` when there are nested attributes (#4783)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug 🪳 Enhancement ✨ Improvement to a component
Projects
None yet
3 participants