-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix remaining typing issues for PyLinter
and related functions
#5663
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
Conversation
pylint/constants.py
Outdated
@@ -24,7 +30,7 @@ | |||
# The line/node distinction does not apply to fatal errors and reports. | |||
_SCOPE_EXEMPT = "FR" | |||
|
|||
MSG_TYPES = { | |||
MSG_TYPES: Final = { |
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.
Although mypy
doesn't recognise that because this dict
is final its values could be seen as Literal
instead of str
it does "allow" us to add an ignore later on.
We will be warned if MSG_TYPES
is being altered and we can thus be sure that any of the values obtained from it is one of the values already in there.
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.
There isn't really a concept of final dicts that I know of (at this time). However there might be two separate workaround that you could try.
Dict[Literal["I", "C", ...], Literal["info", "convention", ...]
That should work but doesn't guarantee a mapping between specific keys and values.- If the mapping is important, I would suggest creating a
TypedDict
and using that instead ofFinal
for the annotation. That only works for string keys, but in this case it could be exactly what you're looking for. (It might even be worth a suggestion on the mypy / pyright issue tracker?)
class M(TypedDict):
I: Literal["info"]
C: Literal["converntion"]
...
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.
It could be that you then need a cast
for message_definition.msgid[0]
though.
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.
@cdce8p Do you know if there is any plans or previous discussion about the concept of final dicts? I saw a similar issue about final frozen sets I think, which would also be beneficial to us.
I can change this to TypedDict
, but if there are plans to (at some point) support final dicts I think it makes sense to keep it like this as it "better" represents what this actually is.
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.
@cdce8p Do you know if there is any plans or previous discussion about the concept of final dicts? I saw a similar issue about final frozen sets I think, which would also be beneficial to us.
I can change this to
TypedDict
, but if there are plans to (at some point) support final dicts I think it makes sense to keep it like this as it "better" represents what this actually is.
I don't know of any plans. If TypedDicts
work, they might be the easiest solution here.
@@ -537,7 +537,7 @@ def make_options() -> Tuple[Tuple[str, OptionDict], ...]: | |||
def __init__( | |||
self, | |||
options=(), | |||
reporter=None, | |||
reporter: Union[reporters.BaseReporter, reporters.MultiReporter, None] = 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.
I wanted to do this in a separate PR but some of the issues fixed here didn't show if I didn't add typing to at least one of these parameters.
@@ -1496,7 +1496,11 @@ def _add_one_message( | |||
msg_cat = MSG_TYPES[message_definition.msgid[0]] | |||
self.msg_status |= MSG_TYPES_STATUS[message_definition.msgid[0]] | |||
self.stats.increase_single_message_count(msg_cat, 1) | |||
self.stats.increase_single_module_message_count(self.current_name, msg_cat, 1) | |||
self.stats.increase_single_module_message_count( | |||
self.current_name, # type: ignore[arg-type] # Should be removable after https://github.com/PyCQA/pylint/pull/5580 |
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.
current_name
shouldn't be None
...
pylint/lint/pylinter.py
Outdated
@@ -1613,6 +1617,9 @@ def _set_one_msg_status( | |||
) -> None: | |||
"""Set the status of an individual message""" | |||
if scope == "module": | |||
if not isinstance(line, 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.
int
is never None
if the scope is module
. I'm not sure what the best way to do this is. If it is not an int
we should indeed raise a ValueError
, but in our current code that's impossible...
Perhaps an assert
? Let me know!
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 asserts are the canonical way to convey "I'm doing that to appease the mypy god".
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.
Yeah, although both assert
and isinstance
will actually decrease performance. I wonder if there is a way to do this without impacting performance..
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.
Some typing can be declared beforehand at no cost (I think we used that for the message id store). But it only work when it's actually impossible for line to not be an int. I think the only way is to change the design which might be costly (and/or overkill).
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.
If you know that it's never None
, just use cast
.
In case it's supposed to always be int
, assert isinstance(line, int)
is probably the better choice. The assert basically checks that some other code didn't mess up.
if isinstance(...)
is more designed to select a code branch in case of true alternatives.
pylint/utils/file_state.py
Outdated
@@ -139,6 +139,8 @@ def handle_ignored_message( | |||
or globally. | |||
""" | |||
if state_scope == MSG_STATE_SCOPE_MODULE: | |||
if not isinstance(line, 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.
Similar problem here: if state_scope == MSG_STATE_SCOPE_MODULE
then line
is never not an int
. How to fix this...?
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.
Isn't it an issue to fix in astroid ?
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.
No this is a pylint issue. If we disable something via pylintrc
its disabled on the "package" scope, that's why it doesn't have a line number. If we disable something in a module itself it will always have an assosciated line number
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.
All right, thank you for clearing that up.
Maybe we could create a different structure to handle module and package disable, but I'm not very clear on the state of the code right now. I'll have a look before 2.13.0 is released. (Or I trust you if you find a solution that you think is good enough in the meantime).
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.
Same here. Either cast
or possibly assert
.
Pull Request Test Coverage Report for Build 1800595762
💛 - Coveralls |
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 we can merge, the isinstance won't be the main contributor to pylint being slow and a refactor of the design would take a long time. We can benefit from the typing before that.
Perhaps @cdce8p knows a good trick for this? |
I left some comments. Check the conversations above 🔝 |
pylint/constants.py
Outdated
@@ -24,7 +30,7 @@ | |||
# The line/node distinction does not apply to fatal errors and reports. | |||
_SCOPE_EXEMPT = "FR" | |||
|
|||
MSG_TYPES = { | |||
MSG_TYPES: Final = { |
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.
@cdce8p Do you know if there is any plans or previous discussion about the concept of final dicts? I saw a similar issue about final frozen sets I think, which would also be beneficial to us.
I can change this to
TypedDict
, but if there are plans to (at some point) support final dicts I think it makes sense to keep it like this as it "better" represents what this actually is.
I don't know of any plans. If TypedDicts
work, they might be the easiest solution here.
pylint/lint/pylinter.py
Outdated
@@ -1631,6 +1635,8 @@ def _set_one_msg_status( | |||
) -> None: | |||
"""Set the status of an individual message""" | |||
if scope == "module": | |||
assert isinstance(line, 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.
I was wondering, would it make sense to add a comment along the lines
assert ... # line should always be int inside module scope
pylint/utils/file_state.py
Outdated
@@ -139,6 +139,8 @@ def handle_ignored_message( | |||
or globally. | |||
""" | |||
if state_scope == MSG_STATE_SCOPE_MODULE: | |||
assert isinstance(line, 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.
Same here
|
Type of Changes
Description
These are the final changes necessary to add typing to the
__init__
ofPyLinter
.I'll comment on some of the changes as these are a bit all over the place.