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
New violation: Prefer TypeError for unexpected types #34
New violation: Prefer TypeError for unexpected types #34
Conversation
Seems like you're on the right track, great job so far!. (w.r.t. specific tryceratops conventions @guilatrova should review). Assuming that the base case works well, we should ensure that the approach is conservative enough not to raise false positives.
There are also a couple of opportunities to increase the recall of the approach:
For all of them test cases should be included (especially testing potential edge cases causing false positive). |
Thanks @swellander and @sbrugman for the support. I believe there are still some improvements to do as mentioned by @sbrugman , but I'm glad to see we're moving forward! I can also contribute more if needed, let me know how's your time! :) |
@sbrugman @guilatrova Thank you for the helpful reviews! You both raise some good points 😉 I'm already learning a lot. I have some time now to go over these comments and will try to have at least some revisions posted later tonight (PST). |
9bb112a
to
906c154
Compare
def incorrect_multiple_operators(some_arg): | ||
if (False and (3 == 3)) or (not isinstance(some_arg, int) and True): | ||
pass | ||
else: | ||
raise Exception("...") # should be typeerror |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this case but am actually curious whether or not you all think it should be included.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that will be tough to ensure we understood the developer's intention. (We might get it sometimes, and miss other times).
I'd prefer to remove it.
Thoughts? @sbrugman
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Boolean functions including isinstance
are endless and not guaranteed to be exclusively type checking. I'd opt for restricting ourselves to the clear-cut cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, strict cleat-cut cases like:
if isinstance()
if not isinstance()
if isinstance() and isinstance()
if isinstance() and not isinstance()
if isinstance() and not isinstance() and isinstance()
- ...
(and variations you did e.g. callable
, issubclass
, etc...)
WDYT @swellander ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense to me 👍
Ok I think i've addressed both of your comments. Lmk if I missed anything. |
906c154
to
f91acb6
Compare
raise Exception("...") # should be typeerror | ||
|
||
|
||
def incorrect_elif(some_arg): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not as obvious to me. I'd suggest to restricting where all conditions consist of one of the type checking functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reviewing it again, I agree with @sbrugman
It's hard to tell the exception being raised there was caused by the type checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed!
We're in great shape here! Feel free to convert from a draft to a real PR. Great work, I'm impressed by:
Lastly, great work on rebasing your git history without I even asking. You're clearly a great developer! 👏 |
|
||
|
||
def incorrect_ArithmeticError(some_arg): | ||
if isinstance(some_arg, int): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps parametrize all these tests using the exception types for simplicity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I run it on some private code I have here, and I'm surprised it catch some violations that I implemented haha (which is good!).
There was one case that failed though (fake alert), let me share so we can start discussing:
Case 1 (failing)
if val is not None and (
not isinstance(val, int) or val < 0
):
raise ValueError(...) # <- Got alert
I think for this case we can't mark errors because the condition is complex and contains validation unrelated to type.
Which is in sync with out discussion: #34 (comment)
Also, let's have a unit test to validate we ignore this scenario.
Thoughts? @swellander
Case 2 (Worked, but we should have a test)
Example 1: (We should support)
if isinstance(val1, type1) and isinstance(val2, type2):
...
else:
raise ValueError("...")
Example 2: (It would be nice to support, I'm ok if we skip it for now)
if not (isinstance(sources, list) and
all(isinstance(v, str) for v in sources)):
raise AssertionError("'sources' must be a list of strings")
If ALL conditions for an if are checking the type, then we mark as violation!
Remove a useless else
As mentioned here: #34 (comment)
Great work so far! Let me know if you have the bandwidth to finish the implementation as suggested above @swellander
@swellander How's your time? Do you think you will be able to make the final adjustments? |
@guilatrova yes, sorry again for the delay! I will have time to submit work tomorrow night if that works for you. If not, I don't want to hold up the project |
@guilatrova @sbrugman I think I've addressed all your comments except for the parametrization of tests and the Case 2, Example 2 from your review, @guilatrova. I hope to get to those two changes tomorrow night. Thanks! P.S. I pushed these latest changes under their own commit for simplicity, but will squash them together with the others once approved. |
for non_type_exception in STANDARD_NON_TYPE_ERROR_IDS: | ||
with tempfile.NamedTemporaryFile(mode="r+") as tmp: | ||
file_content = ( | ||
"if isinstance(some_arg, int):\n" | ||
" pass\n" | ||
"else:\n" | ||
f' raise {non_type_exception}("...")' | ||
) | ||
tmp.write(file_content) | ||
tmp.seek(0) | ||
content = tmp.read() | ||
|
||
tmp_tree = ast.parse(content) | ||
tmp_violations = analyzer.check(tmp_tree, "filename") | ||
|
||
assert len(tmp_violations) == 1 | ||
assert_type_error(4, 9, tmp_violations[0]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbrugman is this what you had in mind when you mentioned parametrizing these tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think he meant to try pytest.mark.parametrize
. But I'm not confident it would make the code any easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, thanks
@swellander Once the commits are "done", then I'll run on some private repos to sanity check. |
Versioninig question@sbrugman @swellander I'm thinking it should. Mostly because anyone that was using the SuggestionIf you agree, @swellander do you mind making your commit |
And treating this feature as a breaking change makes sense to me! I'll use the breaking change syntax when I squash the commits together here shortly. |
09dc984
to
4bf53dd
Compare
4bf53dd
to
d9f8116
Compare
I just tested here against some real code and it looks working. |
I don't have enough words to express my gratitude. Thank you @sbrugman and @swellander for helping the community and for contributing to making everyone's Python code better ❤️ ✨ |
Thank you @sbrugman for the wise input and thank you @guilatrova for the helpful feedback and your positive, welcoming energy 🌞 ! |
This currently only covers the one example case outlined by @sbrugman in issue #32. I want to make sure that I'm on the right track. Thanks!