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

Enhance the protocol checker #3259

Merged
merged 8 commits into from
Nov 27, 2019

Conversation

craig-sh
Copy link
Contributor

@craig-sh craig-sh commented Nov 18, 2019

Steps

  • Add yourself to CONTRIBUTORS if you are a new contributor.
  • Add a ChangeLog entry describing what your PR does.
  • If it's a new feature or an important bug fix, add a What's New entry in doc/whatsnew/<current release.rst>.
  • Write a good description on what the PR does.

Description

Checks return values of protocol functions to see if they are valid types

TODO

  • __index__ returned non-int
  • __repr__ must return string
  • __str__ must return string
  • __bytes__ must return bytes
  • __getnewargs_ex__ should return a tuple of length 2
  • first item of __getnewargs_ex__ must be a tuple
  • second item of __getnewargs_ex__ must be a dict
  • __getnewargs__ should return a tuple
  • __bool__ should return bool
  • __hash__ should return integer
  • __lenght_hint__ must be an integer
  • __length_hint__ should return >=0
  • __format__ must return string

Type of Changes

Type
✨ New feature
🔨 Refactoring

Related Issue

Closes #560

Copy link
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.

Thank you for the PR @craig-sh ! This is going in the right direction, but I left a comment with an improvement we can make to type detection.

if not inferred or inferred is astroid.Uninferable:
return

if not isinstance(inferred, astroid.Instance) or inferred.name != "bool":
Copy link
Contributor

Choose a reason for hiding this comment

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

You can use a Const node to check if it returns bools or not:

Suggested change
if not isinstance(inferred, astroid.Instance) or inferred.name != "bool":
if not isinstance(inferred, astroid.Const) or not isinstance(inferred.value, bool):
....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I'll update my changes!

@coveralls
Copy link

coveralls commented Nov 18, 2019

Coverage Status

Coverage decreased (-0.001%) to 89.824% when pulling bd6cd26 on craig-sh:enhance_protocol_checker into 01dfa52 on PyCQA:master.

@PCManticore
Copy link
Contributor

Hey @craig-sh Let me know if this is ready for review. 😄 You can also switch it from the draft mode.

@PCManticore
Copy link
Contributor

Or are you planning to write all the checks from the TODO? No worries if that's the case, was just wondering.

@craig-sh
Copy link
Contributor Author

Hey, yeah Im just finishing the rest of the checks in the TODO

@craig-sh
Copy link
Contributor Author

I've implemented all the protocol functions mentioned in #560 . Just a couple of notes

I followed the behavior of the existing __len__() non-negative integer checks where a bool is treated as a valid integer. While it's techically correct should this be flagged as a seperate warning?

I was not sure how to go about checking the elements of a tuple returned by a function invoking tuple(...) .

For example this PR will raise an error for def __getnewargs_ex__(self): return ("x", "y") but not for def __getnewargs_ex__(self): return tuple("x", "y").

Is this behavior fine or could you help point me in the right direction to fix it?

Thanks!

@craig-sh craig-sh marked this pull request as ready for review November 22, 2019 00:28
@craig-sh craig-sh changed the title (WIP) Enhance the protocol checker Enhance the protocol checker Nov 22, 2019
Copy link
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.

Hey @craig-sh This is awesome work, thank you! Left a bunch of comments, overall looks great. Once those are fixed, we can pull this in.

ChangeLog Outdated
@@ -7,6 +7,11 @@ What's New in Pylint 2.5.0?

Release date: TBA

* Added errors for some protocol functions (`__hash__`, `__index__`, etc) when
Copy link
Contributor

Choose a reason for hiding this comment

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

Would be useful to list all the new errors that were added in the description part of this entry.

@@ -53,3 +53,6 @@ separated list of regexes, that if a name matches will be always marked as a bla
* Mutable ``collections.*`` are now flagged as dangerous defaults.

* Add new --fail-under flag for setting the threshold for the score to fail overall tests. If the score is over the fail-under threshold, pylint will complete SystemExit with value 0 to indicate no errors.

* Add errors for invalid return types from functions ``__index__``, ``__repr__``, ``__str__``, ``__bytes__``,
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's list the new checks as well. Also it should go under the New Checkers section.

inferred = _safe_infer_call_result(node, node)
# Only want to check types that we are able to infer
if inferred and inferred is not astroid.Uninferable:
call_map = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's move this dict construction at __init__ level since otherwise we'll create it over and over for every function we visit (which might be a lot for large projects)


inferred = _safe_infer_call_result(node, node)
# Only want to check types that we are able to infer
if inferred and inferred is not astroid.Uninferable:
Copy link
Contributor

Choose a reason for hiding this comment

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

The second part is superfluous, astroid.Uninferable is falsey in nature.

if SpecialMethodsChecker._is_wrapped_type(node, "int"):
return True

if isinstance(node, astroid.Const) and isinstance(node.value, int):
Copy link
Contributor

Choose a reason for hiding this comment

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

Can simply these three lines to:

Suggested change
if isinstance(node, astroid.Const) and isinstance(node.value, int):
return isinstance(node, astroid.Const) and isinstance(node.value, int)

"__bool__ does not return bool",
"invalid-bool-returned",
"Used when a __bool__ method returns something which is not a bool",
{},
Copy link
Contributor

Choose a reason for hiding this comment

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

The fourth dict argument should not be needed, let's try removing it.

@craig-sh
Copy link
Contributor Author

craig-sh commented Nov 27, 2019

Hey @PCManticore , thanks for the review! I added the fixes you mentioned, please let me know if there is anything else I can change :)

Thanks!

if SpecialMethodsChecker._is_wrapped_type(node, "str"):
return True

if isinstance(node, astroid.Const) and isinstance(node.value, str):
Copy link
Contributor

Choose a reason for hiding this comment

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

These can all be changed to return isinstance(....)

@PCManticore PCManticore merged commit c632201 into pylint-dev:master Nov 27, 2019
@PCManticore
Copy link
Contributor

Hey @craig-sh Thank you for sending this PR! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[easy] Enhance the protocol checker
3 participants