Skip to content

Prohibit list[int], etc (those fail at runtime) #2869

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

Merged
merged 16 commits into from
Mar 14, 2017

Conversation

ilevkivskyi
Copy link
Member

Fixes #2428

All of the following are now allowed, but fail at runtime:

list[int]
dict[int, str]
set[int]
tuple[int]
frozenset[int]
enumerate[int]
collections.defaultdict[int, str]
collections.Counter[str]
collections.ChainMap[str, str]

I prohibit those by simply tracking whether a corresponding symbol table node was normalized or not.

@gvanrossum I make an exclusion for stubs, because a have found dozens of places where this is used in typeshed, if you think that it also makes sense to prohibit this in stubs, then I will make an additional PR to typeshed.

mypy/nodes.py Outdated
@@ -2143,17 +2152,20 @@ class SymbolTableNode:
# For deserialized MODULE_REF nodes, the referenced module name;
# for other nodes, optionally the name of the referenced object.
cross_ref = None # type: Optional[str]
# Was this node created by normaloze_type_alias?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalize

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Fixed.

@JelleZijlstra
Copy link
Member

Would this approach also allow you to fix #999? Jukka said before that these two should be closely related. I tried a little while ago to fix #999 but couldn't find a good way to do it.

@gvanrossum
Copy link
Member

Actually #999 was fixed yesterday by #2863.

@JelleZijlstra
Copy link
Member

Oh, that's great! Sorry for missing that.

@gvanrossum
Copy link
Member

Thanks! This found tons of bad annotations in our internal codebase.

FYI I am currently in a state where I can still write code but cannot focus long enough to review anything more complex than simple typeshed fixes. Hopefully this is not a permanent condition, but I expect it to persist for a few weeks...

@ilevkivskyi
Copy link
Member Author

Thanks! This found tons of bad annotations in our internal codebase.
FYI I am currently in a state where I can still write code but cannot focus long enough to review anything more complex than simple typeshed fixes. Hopefully this is not a permanent condition, but I expect it to persist for a few weeks...

I am glad this is helpful. I hope you get better soon.

@ilevkivskyi
Copy link
Member Author

@gvanrossum It turns out mypy itself uses collections.deque[State] in build.py.

@ambv
Copy link
Contributor

ambv commented Mar 10, 2017

Hold on.

What's the value added from making this change? The "failure at runtime" in this case is actually "failure at import" which is pretty obvious to the user.

In fact, I've been quietly hoping that once type checks get uncontroversial enough, we can revisit the ideas for Python 3.7 to either:

  • remove annotations at runtime altogether; or
  • make them all strings (listed as a possible future enhancement in PEP 484).

In both cases, we could start using generics on builtin collections without requiring runtime changes to the relevant C code. This makes for a big improvement in how obvious type hints look like to the reader. The proliferation of this problem in typeshed, internal codebases using type checks, proves that this is a problem. I'm getting support requests around this sometimes, and sometimes I myself make mistakes (either making a lowercase dict or an uppercase Str!).

So, I'll open a new issue on python/typing about the 3.7 enhancement. In the mean time, can we defer merging this?

@ilevkivskyi
Copy link
Member Author

ilevkivskyi commented Mar 10, 2017

@ambv
There are still two places where defaultdict[int, str] etc. will fail at runtime even if annotations are strings - base class list and instantiation:

from collections import defaultdict, Counter, ChainMap
from typing import Any

class MyDDict(defaultdict[int, str]): # Fails at runtime, mypy is silent
    ...
class CustomList(list[int]): # Same here, fails at runtime, mypy is silent
    ...

c = Counter[int]() # Same situation here
m = ChainMap[str, Any]() # ... and here

In principle I am in favour of making annotations just strings (but rather via a flag or __future__ import), but still I would like mypy to warn about the above code. If such code appears inside functions it will be "failure late after import".

This PR will make all these errors, I think it is reasonable.

@ambv
Copy link
Contributor

ambv commented Mar 10, 2017

Suggestion reported as python/typing#400. Nice round number.

Your examples are valid concerns. Especially your generic base class examples are tricky. He we will require users to continue using the typing variants of collections even if python/typing#400 gets implemented.

Regarding your last two examples, mypy should warn here too but ultimately they are solved by using variable annotations or type comments. The suggestion in PEP 484 to instantiate like that needs revision, we should probably discourage further use of that syntax. It's confusing to users why DefaultDict[int, bytes]() is okay but List[int]() is invalid.

Does it make sense for your PR to only focus on raising errors for those cases?

@ilevkivskyi
Copy link
Member Author

we should probably discourage further use of that syntax

DefaultDict[int, bytes]() is just a "syntactic sugar" to avoid duplicating the long name. I think PEP 484 should not prohibit this, the user could decide what to use, the shorthand form or the full form:

var: DefaultDict[int, str] = defaultdict()

The PEP already makes the full form preferable: "It is not recommended to use the subscripted class (e.g. Node[int]) directly in an expression".

Does it make sense for your PR to only focus on raising errors for those cases?

It is a bit tricky but possible. However, I think it could be surprising for a user why this is flagged in some places but not in others. Also Jukka have pointed few more places where this will fail: in cast, in TypeVar, and in NewType.

@gvanrossum
Copy link
Member

I really want this to land. It's the right thing from the PEP's POV -- writing list[int] instead of List[int] is just bad form. This has long been on my list of mypy annoyances.

I'm going to review the code now and unless I see something terrible I will merge it.

Re: DefaultDict etc., I think we should really stick to our guns and disallow instantiation just like we disallow it for List etc., the reasons are the same -- these are generic "aliases" for concrete implementations (and that sets them apart from user-defined generic classes like Node as well as ABC's like Sequence). But that doesn't need to happen in the same PR.

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some nits, really.

@@ -1283,13 +1286,19 @@ def process_import_over_existing_name(self,

def normalize_type_alias(self, node: SymbolTableNode,
ctx: Context) -> SymbolTableNode:
normalized = False
if node.fullname in type_aliases:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only place I can find where frozenset is treated differently -- do you understand why?

mypy/nodes.py Outdated
collections_type_aliases.items()) # type: Dict[str, str]

nongen_builtins = {'builtins.tuple': 'typing.Tuple',
'builtins.frozenset': 'typing.FrozenSet',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious why this isn't in type_aliases above. How is frozenset different from set?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it looks like an old oversight. I just checked stubs for builtins.frozenset and typing.FrozenSet, the former is much better (it has all actual methods). I can easily fix this, but this requires a tiny PR to typeshed to be merged first and synced python/typeshed#979

Could you please merge it?

[case testNoSubscriptionOfBuiltinAliases]
from typing import List, TypeVar

list[int]() # E: "list" is not subscriptable, use "typing.List" instead
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is somewhat sad, because if you follow the instructions in the message, you get List[int]() which elicits a different error ("Type List cannot be instantiated; use list() instead"), but at runtime.

reveal_type(fun()) # E: Revealed type is 'builtins.list[builtins.int]'

BuiltinAlias = list
BuiltinAlias[int]() # E: "list" is not subscriptable, use "typing.List" instead
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

_program.py:5: error: "defaultdict" is not subscriptable, use "typing.DefaultDict" instead
_program.py:6: error: "Counter" is not subscriptable, use "typing.Counter" instead
_program.py:9: error: "defaultdict" is not subscriptable, use "typing.DefaultDict" instead
_program.py:12: error: Invalid index type "int" for "dict"; expected type "str"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, why does this refer to dict instead of to DefaultDict?

@gvanrossum
Copy link
Member

As I am chugging through our internal codebase fixing the issues found here, I've noticed that the most common is defaultdict -- presumably because (a) that's a pretty useful data type, and (b) older versions of typing.py don't have it.

@ilevkivskyi
Copy link
Member Author

I have made a PR to typeshed fixing some stubs python/typeshed#993

In the meantime I have found something more, something like this

from collections.abc import Iterable

it: Iterable[int]

is not flagged even with this PR. I understand the reason, but it is a bit more tedious to fix. I propose to make it in a separate PR.

@gvanrossum gvanrossum merged commit 4ca67f9 into python:master Mar 14, 2017
@gvanrossum
Copy link
Member

Merged! I am about halfway through changing our internal codebase and have not found additional complications.

@ilevkivskyi
Copy link
Member Author

Thanks! I will open a separate issue about collections.abc.Iterable[int] (I don't think it is a priority, just not to forget about it).

@gvanrossum
Copy link
Member

Hm. There's another issue, which is that this introduces the use of TYPE_CHECKING in mypy itself. This is unfortunately a problem with our internal installation that I can't easily fix, and we've in the past worked around this (using MYPY = False; if MYPY: ...).

@ilevkivskyi
Copy link
Member Author

This is really a temporary measure until the new version of typing (with Deque) is on PyPI. Normally it was released around the same time as the next Python version.

Python 3.6.1 final is scheduled for Monday, but on the other hand we don't plan to make any changes to typing until that time (e.g. NoReturn will go into 3.6.2). So you could make the PyPI release sooner if necessary, and then I will make a PR removing if TYPE_CHECKING: ....

@gvanrossum
Copy link
Member

No, the use of if TYPE_CHECKING itself inside mypy is the problem. We really need that to go back to if MYPY. I'll do it.

@gvanrossum
Copy link
Member

Also the reason (which I suddenly recall) is that we're running mypy with Python 3.5.1 which forces one to use the stdlib typing.py which doesn't even have TYPE_CHECKING.

@gvanrossum
Copy link
Member

Fix in #3008

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants