-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add --warn-implicit-any #3141
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 --warn-implicit-any #3141
Conversation
Since you say "experimental" I'm not reviewing this yet and I've added "[WIP]" to the subject. (I think GMail may even thread notifications correctly because of the brackets.) |
It's far from perfect, but since it caught quite a few unintentional |
I propose that @ddfisher should review 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.
I provided some cosmetic feedback but I unfortunately, I can't give any deep review since my knowledge of mypy is limited. Perhaps, @ddfisher can help.
Overall, I think this feature is great! We should try to finish it up and merge it.
Maybe we can name the flag --warn-implicit-any-generic
or --disallow-implicit-any-generic
.
mypy/semanal.py
Outdated
@@ -3251,12 +3251,15 @@ def name_already_defined(self, name: str, ctx: Context) -> None: | |||
self.fail("Name '{}' already defined".format(name), ctx) | |||
|
|||
def fail(self, msg: str, ctx: Context, serious: bool = False, *, | |||
blocker: bool = False) -> None: | |||
blocker: bool = False, implicit_any: bool = False) -> 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.
It seems like adding an extra parameter to fail function might not be the best way to do it.
I would try to keep this function generic for any kind of failures.
Perhaps, you can check if the error is in the caller.
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 agree. Unfortunately, I need to suppress the error message based on the information (options
, and whether the module being checked is a stub) that's unavailable inside TypeAnalyser
methods where the error occurs. I am not sure what the best fix for this would be. If I don't add an extra parameter to fail
, I'll have to pass all this information into TypeAnalyser
, but that's not good either since TypeAnalyser
should not care about those details.
@@ -3680,7 +3683,14 @@ def analyze(self, type: Type) -> None: | |||
analyzer = TypeAnalyserPass3(self.fail) | |||
type.accept(analyzer) | |||
|
|||
def fail(self, msg: str, ctx: Context, *, blocker: bool = False) -> None: | |||
def fail(self, msg: str, ctx: Context, *, blocker: bool = False, | |||
implicit_any: bool = False) -> 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.
Same as above - not sure if it's good to have an implicit_any parameter.
if not self.options.warn_implicit_any or self.errors.file.endswith('.pyi'): | ||
return | ||
# TempNode, so must have already reported in the first pass | ||
if ctx.get_line() == -1: |
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 seems like a hack but I couldn't find a way to do it better. Perhaps, someone with more mypy experience may help with that?
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.
Are you talking about checking for stubs or checking for temporary nodes? I don't know if I would cosnider either of them a hack (my threshold of hackiness in the error-reporting code is much higher than in normal code); but if anyone has a better solution, it would be nice.
@@ -174,6 +175,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: | |||
return self.analyze_callable_type(t) | |||
elif fullname == 'typing.Type': | |||
if len(t.args) == 0: | |||
self.implicit_any('Type without type args', t) |
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 doesn's seem to be a test case for this. It's probably worth having one.
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.
Added
@@ -183,6 +185,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: | |||
if self.nesting_level > 0: | |||
self.fail('Invalid type: ClassVar nested inside other type', t) | |||
if len(t.args) == 0: | |||
self.implicit_any('ClassVar without type args', t) |
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 doesn's seem to be a test case for this. It's probably worth having one.
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.
Added
@@ -220,6 +224,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: | |||
# context. This is slightly problematic as it allows using the type 'Any' | |||
# as a base class -- however, this will fail soon at runtime so the problem | |||
# is pretty minor. | |||
self.implicit_any('Assigning value of type Any', t) |
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 doesn's seem to be a test case for this. It's probably worth having one.
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.
Added
mypy/typeanal.py
Outdated
@@ -259,6 +264,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: | |||
fallback=instance) | |||
return instance | |||
else: | |||
self.implicit_any('Fallback', t) |
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 seems to be a debug statement.
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.
Hmm, how can you tell that this cannot happen in a production run?
def fail(self, msg: str, ctx: Context, *, blocker: bool = False, | ||
implicit_any: bool = False) -> None: | ||
if implicit_any: | ||
if not self.options.warn_implicit_any or self.errors.file.endswith('.pyi'): |
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 should be a better way for checking for stub files but I can't seem to find it.
Why are you limiting the flag to non-stub files anyway?
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 also can't find it.
In stub files, a lot of the times the type argument in genertic types is omitted (e.g., Union[type, Tuple]
, def f() -> tuple:
, def g() -> Iterable:
, etc.). There'll be hundreds of warnings like this, making the flag unusable.
I am sure we don't want to change the stubs to avoid implicit Any
(and even if we did, that change would have to happen first, before warn_implicit_any
starts paying attention to stubs).
Just remembered this doesn't cover the cases when not all type parameters are specified. So like |
What do you mean by this? |
@ilevkivskyi you are right. I looked at the code and it didn't seem like it would handle these kind of errors. But I just ran mypy now and it is an error. |
@pkch are you interested in working on this? |
Ah yes, I'm going to make the fixes I'm aware of. However, I'm still not sure if the overall approach is acceptable. |
(--disallow-any=generics) This code is based on python#3141 by pkch. This option disallows implicit Anys from omitted type parameters to generic types. For instance, `def x() -> List` would produce an error while `def x() -> List[Any]` is allowed. Note that with the flag enabled builtin generic types such as `list` and `set` are also forbidden.
I reworked this PR and submitted it as #3637. Closing this PR in favor of the new one. |
(--disallow-any=generics) This code is based on #3141 by pkch. This option disallows implicit Anys from omitted type parameters to generic types. For instance, `def x() -> List` would produce an error while `def x() -> List[Any]` is allowed. Note that with the flag enabled builtin generic types such as `list` and `set` are also forbidden.
Experimental: attempt to address parts of #2901