-
Notifications
You must be signed in to change notification settings - Fork 65
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
Implement CollapseIsinstanceChecksRule #116
Implement CollapseIsinstanceChecksRule #116
Conversation
Codecov Report
@@ Coverage Diff @@
## master #116 +/- ##
==========================================
+ Coverage 83.75% 84.30% +0.55%
==========================================
Files 73 77 +4
Lines 2905 3199 +294
==========================================
+ Hits 2433 2697 +264
- Misses 472 502 +30
Continue to review full report at Codecov.
|
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.
Looks like a great lint rule, thanks for working on it. I only have one comment.
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.
LGTM, awesome work :)
Glad to hear that, thanks! |
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.
@isidentical , this is a great rule that makes code more efficient and more simpler.
I've used this to optimize some hot path at work.
The code is 100% tested. Awesome job!
https://codecov.io/gh/Instagram/Fixit/pull/116/diff?src=pr&el=tree#diff-Zml4aXQvcnVsZXMvY2hhaW5lZF9pbnN0YW5jZV9jaGVjay5weQ==
Other than isinstance
, some other Python builtin methods can also take a Tuple to combine multiple checks e.g. startswith
and endswith
of str. The pattern is slightly different (it needs to be a str.startswith
, so probably better to build as another lint rule).
Exactly, that is what I was thinking, you basically read my mind! |
Co-authored-by: Jimmy Lai <[email protected]>
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.
CollapseIsInstanceChecksRule (with capital I
) also looks good to me if you like it more.
for context in self.get_metadata(QualifiedNameProvider, func): | ||
if ( | ||
context.name != "builtins.isinstance" | ||
or context.source is not QualifiedNameSource.BUILTIN | ||
): | ||
return None |
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.
Why not simply use QualifiedNameProvider.has_name(self, name="builtins.isinstance", QualifiedNameSource.BUILTIN)
to be simpler?
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.
Also, this checks looks redundant since we already check isinstance
in _collect_isinstance_calls
and we can simply use QualifiedNameProvider.has_name(self, name="builtins.isinstance", QualifiedNameSource.BUILTIN)
in _collect_isinstance_calls
.
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.
AFAIK has_name
check for any appearance, but we want to ensure that it is always the isinstance
we found (just like all
instead of any
). I don't know though if it is the right way or not, can change?
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, has_name
checks if any of the qualified name matches and that's how we usually checks the qualified names. https://libcst.readthedocs.io/en/latest/metadata.html#libcst.metadata.QualifiedNameProvider
The QualifiedName
analysis is based on scope analysis to find assignments from parent scopes and it returns all possible assignments. Sometimes when the code has branches with redefinition of the same name, it could return multiple qualified names. Use all
can miss lint suggestion on those cases while use any
can provide the correct suggestion based on our experience.
E.g.
https://github.com/Instagram/LibCST/blob/7ca738bf3964ac48ebb36b1b362153c87c29a706/libcst/codemod/commands/rename.py#L262-L267
https://github.com/Instagram/LibCST/blob/c023fa7c4caff3fd2b3946080f9a58b539b10363/libcst/codemod/commands/convert_namedtuple_to_dataclass.py#L31-L47
def foo(): | ||
def isinstance(x, y): | ||
return _foo_bar(x, y) | ||
if isinstance(x, y) or isinstance(x, z): |
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.
Why this is not collapsed? This is basically the same as the first Invalid test case and should be collapsed.
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.
Did you see the function def isinstance(x, y):
that redefines isinstance
name in the current scope?
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 missed that. It makes sense now!
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.
Looks great!
Currently the rule only consider the bare isinstance and or operator. In some other collapse-able case like not isinstance(...) and not isinstance(...)
which can be collapsed as not isinstance(..., [...])
. I think that's a less common use case and can leave as a future enhancement if you like to improve it.
Found another builtin function issubclass
can also takes a Tuple but it's an all
check (unlike the any
check in isinstance). So issubclass(x, y) and issubclass(x, z)
can be collapsed. That can be another lint rule by reusing the unwrap and collect_targets helpers provided in this lint rule.
def foo(): | ||
def isinstance(x, y): | ||
return _foo_bar(x, y) | ||
if isinstance(x, y) or isinstance(x, z): |
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 missed that. It makes sense now!
I am aware of |
Just confirmed that it's an @isidentical would you like to refactor |
I think it suits to the purpose, so I would be OK with it. No need to mirroring the whole logic. If it sounds nice to you, I can just send a very small / simple PR for that. |
Yes, please go head, I'm happy to review and merge it. |
Summary
becomes
Test Plan