-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Option to disallow explicit Any types (--disallow-any=explicit) #3579
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
(--disallow-any=explicit) Type aliases are a little tricky to deal with because they get "inlined". The solution was to unmark Anys as explicit right after we check a type alias for explicit Anys.
# Conflicts: # mypy/main.py # mypy/semanal.py
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.
Largely looks good! Thanks for the the thorough testing! Have a few nits.
mypy/messages.py
Outdated
@@ -872,6 +872,9 @@ def unimported_type_becomes_any(self, prefix: str, typ: Type, ctx: Context) -> N | |||
self.fail("{} becomes {} due to an unfollowed import".format(prefix, self.format(typ)), | |||
ctx) | |||
|
|||
def explicit_any(self, location: str, ctx: Context) -> None: | |||
self.fail('Explicit "Any" is not allowed in a {}'.format(location), ctx) |
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 wonder if this error would be clearer if it was just Explicit "Any" is not allowed.
. This reads to me like it's implying that the Any
is not allowed in that particular location, but in fact it's not allowed in any location.
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, actually, I agree. I think I was trying to be too fancy when it wasn't necessary.
mypy/semanal.py
Outdated
@@ -4310,3 +4334,21 @@ def find_fixed_callable_return(expr: Expression) -> Optional[CallableType]: | |||
if isinstance(t.ret_type, CallableType): | |||
return t.ret_type | |||
return None | |||
|
|||
|
|||
def unmark_any_as_explicit(t: Type) -> 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 think this name (and that of the associated class) needs a bit more thought -- I don't think it's clear what it means outside of the context of this PR.
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, naming things is hard - I went through several iterations of names trying to name this function.
How about unset_any_type_as_explicit
? Alternatively, we could try to make the function more generic and add a bool
parameter explicit
. Something like:
def set_any_type_explicit_attribute(explicit: bool) -> None:
...
line: int = -1, | ||
column: int = -1) -> None: | ||
super().__init__(line, column) | ||
# Was this Any type was inferred without a type annotation? | ||
self.implicit = implicit | ||
# Does this come from an unfollowed import? See --disallow-any=unimported option | ||
self.from_unimported_type = from_unimported_type | ||
# Does this Any come from an explicit type annotation? | ||
self.explicit = explicit |
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.
Is this not always the opposite of implicit
? If not, the specifics need to be made very clear in the 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.
Yes, explicit
is not always the opposite of implicit
, which is confusing.
implicit
refers to situations when Any
was inferred because a variable or parameter was not annotated.
explicit
refers to situations when there was an explicit Any
annotation.
However, if this Any
comes, for instance, from an unimported type, both explicit
and implicit
will be False
.
It seems to me that implicit
has a wrong name. It is currently used for --disallow-untyped-defs
to see if this Any
comes from a parameter or variable with no explicit type annotation.
I think instead of tweaking comments we should rename implicit
. How do you feel about calling it from_unannotated
?
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 comment on implicit to make it more clear.
mypy/semanal.py
Outdated
t.accept(UnmarkAnyAsExplicit()) | ||
|
||
|
||
class UnmarkAnyAsExplicit(TypeQuery[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.
This is a bit of an abuse of the TypeQuery
class. None of its parent classes seem to be set up for doing this easily, but I'm not entirely sure how I feel about this overall. @JukkaL do you think this is a reasonable thing to do?
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, I agree, this does not seem right.
Moreover, this mutates a type, which is also not great.
What I need is a TypeVisitor that visit all types within a type. Maybe I can refactor one from TypeQuery
and then make TypeQuery
inherit the new visitor or something?
test-data/unit/cmdline.test
Outdated
[file m.py] | ||
from typing import Any, List | ||
v: Any = '' | ||
w = [''] # type: List[Any] |
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.
Nit: It's better to vary test cases along one axis at once. Here, you're switching from testing a type annotation to a type comment, but you're also switching from testing a plain Any
to an Any
in a generic class. (And also the list shouldn't need any contents.)
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, that makes a lot of sense.
Also, I will split these tests into two.
|
||
def foo(x: TupleAny[str]) -> None: # no error | ||
pass | ||
|
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.
Could you also check that TupleAny[Any]
produces an error?
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.
Wow, this is an interesting edge case. Will add a test.
test-data/unit/cmdline.test
Outdated
[file m.py] | ||
from typing import Any, List, NewType | ||
|
||
# The commented out line is covered without the flag enabled - it is tested elsewhere |
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'd instead be a bit more specific and say "making a NewType of Any is never allowed".
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.
Yep, that wording is definitely better.
However, I think it's better to include the test (Baz = NewType('Baz', Any)
).
In a different PR Guido pointed out that tests should follow features, not implementation.
And I think in this case, it's better to have an extra tests. Because if this errors stops being reported --disallow-any=explicit
will not work - it won't report an explicit Any.
So I am going to include the test here.
Most feedback should be addressed now. I also realized that the originally this PR did not report errors when |
4332307
to
3d87808
Compare
# Conflicts: # mypy/messages.py # mypy/semanal.py
mypy/semanal.py
Outdated
t.accept(MarkAnyNonExplicit()) | ||
|
||
|
||
class MarkAnyNonExplicit(TypeQuery[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.
@JukkaL need your advice on this.
As David pointed out, this use of TypeQuery
seems like abuse of this class.
What do you think is a good way of dealing with this?
We could refactor a child of TypeQuery
to have that. Or maybe there's another way.
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.
What about having a TypeQuery
that returns all Any
types that are part of a type, and then iterate over the results of the query and set explicit
? I.e. something like this:
for any_type in find_all_any_types(t):
any_type.explicit = False
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 actually that might not be a good idea, since mutating types is only safe if you are certain that there is ever only a single reference to the type object. What about a type transform that replaces Any
instances with a copy that has explicit
set to False
? You should be able to use TypeTranslator
.
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, that's a really good idea!
Done!
All feedback should be addressed now, except for using |
mypy/checker.py
Outdated
@@ -627,6 +627,11 @@ def is_implicit_any(t: Type) -> bool: | |||
if has_any_from_unimported_type(arg_type): | |||
prefix = "Argument {} to \"{}\"".format(idx + 1, fdef.name()) | |||
self.msg.unimported_type_becomes_any(prefix, arg_type, fdef) | |||
if ('explicit' in self.options.disallow_any and |
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 same piece of code gets repeated almost identically many times. Can you refactor this into a function or a few 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.
Yep, done!
to check for explicit Any
# Conflicts: # mypy/semanal.py
All feedback is addressed now, this PR should be ready to be merged. |
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!
Type aliases are a little tricky to deal with because they get "inlined".
The solution was to unmark Anys as explicit right after we check a type alias
for explicit Anys.