Skip to content

Refactor type aliases - #5221

Merged
ilevkivskyi merged 34 commits into
python:masterfrom
ilevkivskyi:refactor-aliases
Jun 25, 2018
Merged

ilevkivskyi merged 34 commits into
python:masterfrom
ilevkivskyi:refactor-aliases

Conversation

@ilevkivskyi

Copy link
Copy Markdown
Member

This refactors type aliases by introducing a dedicated symbol node TypeAlias, and removing all ad-hoc attributes from SymbolTableNode. This simplifies the overall logic, and also the fine grained dependency tracking (since now aliases have names).

Most of the logic is straightforward. Few comments:

  • It looks like .type_override was also used for some overload hacks, I replaced those with a proper fix.
  • Somehow my changes exposed a small ImportedName bug, I can't find a simple repro (typeshed check test was failing), but the fix is straightforward.
  • In some tests revealed type don't have * anymore, I think this is more consistent.
  • There few other small changes in tests, I think they are harmless.

Comment thread mypy/semanal.py Outdated
return Instance(node, [AnyType(TypeOfAny.special_form)] * len(node.defn.type_vars))

def add_builtin_aliases(self, tree: MypyFile) -> None:
# For historical reasons, the aliases like `List = list` are not defined

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We could just define those in typeshed actually.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I would keep this out of this PR. Will pytype an other be OK with this? (There are potential problems with circular dependencies). If yes we can easily do this later, fortunately with this refactoring it is just a matter of removing this patching step (before normalize_type_alias was all over the semanal code).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe other type checkers don't use typing.pyi from typeshed; they have their own versions.

But I agree that this PR is doing enough things already. :)

JelleZijlstra added a commit to JelleZijlstra/typeshed that referenced this pull request Jun 15, 2018
Currently fails terribly, but maybe python/mypy#5221 will fix it.
@JelleZijlstra

Copy link
Copy Markdown
Member

I found a new crash using this PR's code:

$ cat bin/usesta.py 
from typing import List, TypeVar
T = TypeVar('T')

A = List[T]

from hasta import A as A
$ mypy --show-traceback bin/usesta.py 
bin/usesta.py:6: error: Cannot find module named 'hasta'
bin/usesta.py:6: note: (Perhaps setting MYPYPATH or using the "--ignore-missing-imports" flag would help)
bin/usesta.py:6: error: INTERNAL ERROR -- please report a bug at https://github.com/python/mypy/issues version: 0.620+dev-9459b3a74f0f1fefe28556e67db6a5eec2f98f8c
Traceback (most recent call last):
  File "/Users/jelle/py/venvs/venv37/bin/mypy", line 11, in <module>
    load_entry_point('mypy', 'console_scripts', 'mypy')()
  File "/Users/jelle/py/mypy/mypy/__main__.py", line 7, in console_entry
    main(None)
  File "/Users/jelle/py/mypy/mypy/main.py", line 91, in main
    res = type_check_only(sources, bin_dir, options, flush_errors, fscache)  # noqa
  File "/Users/jelle/py/mypy/mypy/main.py", line 148, in type_check_only
    fscache=fscache)
  File "/Users/jelle/py/mypy/mypy/build.py", line 177, in build
    flush_errors, fscache)
  File "/Users/jelle/py/mypy/mypy/build.py", line 282, in _build
    graph = dispatch(sources, manager)
  File "/Users/jelle/py/mypy/mypy/build.py", line 2436, in dispatch
    process_graph(graph, manager)
  File "/Users/jelle/py/mypy/mypy/build.py", line 2729, in process_graph
    process_stale_scc(graph, scc, manager)
  File "/Users/jelle/py/mypy/mypy/build.py", line 2845, in process_stale_scc
    graph[id].semantic_analysis()
  File "/Users/jelle/py/mypy/mypy/build.py", line 2047, in semantic_analysis
    self.manager.semantic_analyzer.visit_file(self.tree, self.xpath, self.options, patches)
  File "/Users/jelle/py/mypy/mypy/semanal.py", line 296, in visit_file
    self.accept(d)
  File "/Users/jelle/py/mypy/mypy/semanal.py", line 3241, in accept
    node.accept(self)
  File "/Users/jelle/py/mypy/mypy/nodes.py", line 317, in accept
    return visitor.visit_import_from(self)
  File "/Users/jelle/py/mypy/mypy/semanal.py", line 1394, in visit_import_from
    self.add_unknown_symbol(as_id or id, imp, is_import=True)
  File "/Users/jelle/py/mypy/mypy/semanal.py", line 1492, in add_unknown_symbol
    self.add_symbol(name, SymbolTableNode(GDEF, var), context)
  File "/Users/jelle/py/mypy/mypy/semanal.py", line 3154, in add_symbol
    self.name_already_defined(name, context, existing)
  File "/Users/jelle/py/mypy/mypy/semanal.py", line 3206, in name_already_defined
    elif node and node.line != -1:
AttributeError: line
bin/usesta.py:6: : note: use --pdb to drop into pdb

(It crashes the same way if it can find hasta.)

@ilevkivskyi

Copy link
Copy Markdown
Member Author

@JelleZijlstra Thanks! TBH I am surprised there is only one crash like this, line and column are never set on TypeAlias in this PR. Will fix this now.

@JukkaL JukkaL left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for refactoring this! This makes the type alias implementation much cleaner, and also saves some memory since a bunch of attributes are no longer needed. Apparently a few minor bugs were fixed in as well, which is nice.

Left a bunch of minor comments (mostly nits).

Comment thread mypy/build.py Outdated
graph[id].fix_suppressed_dependencies(graph)
if 'typing' in scc:
# For historical reasons we need to manually add typing aliases
# for built-in generic collections, see docstring for details.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Update comment to be more explicit about which docstring this refers to.

Comment thread mypy/checkexpr.py Outdated
"""Type check a type application (expr[type, ...]).

There are two different options here, depending on whether expr refers
to a type alias or directly to a generic class. In first case we need

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Grammar nit: "In the first case ..."

Comment thread mypy/checkexpr.py Outdated
to a type alias or directly to a generic class. In first case we need
to use a dedicated function typeanal.expand_type_aliases. This
is due to the fact that currently type aliases machinery uses
unbound type variables, while normal generics use bound ones,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Grammar nit: use period or semicolon (e.g. "... bound ones; see ...").

Comment thread mypy/checkexpr.py Outdated
A = reveal_type(List[T])
reveal_type(A)

will reveal the same type `def (...) -> builtins.list[Any]`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is a bit unclear. What does "same" mean here? If both of the reveal_types reveal the same types, it would be better to rewrite this as "Both reveal_type instances will reveal the same type ...".

Also, it's worth briefly discussing why the type is list[Any] and not list[T] (because of implicit Any for missing type args?).

Comment thread mypy/checkexpr.py Outdated
"""
return self.alias_type_in_runtime(alias.type, alias.tvars, alias.no_args, alias)

def alias_type_in_runtime(self, target: Type, alias_tvars: List[str],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The name of this method is a bit unclear. Maybe rename to alias_type_in_runtime_context, for example?

Comment thread test-data/unit/check-generics.test Outdated
UA = Union[T, int]

cs = CA[str] + 1 # E: Unsupported left operand type for + ("Type alias to Callable")
cs = CA + 1 # E: Unsupported left operand type for + ("Type alias to Callable")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we still test also CA[str] + 1, or it no longer relevant?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It is less relevant because it will fail with something different like "Type application doesn't target a class" in all three cases. But I will add one more test for this.

Comment thread test-data/unit/check-generics.test Outdated
reveal_type(cs) # E: Revealed type is 'Any'

ts = TA[str]() # E: "Type alias to Tuple" not callable
ts = TA() # E: "Type alias to Tuple" not callable

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similar to above.

Comment thread test-data/unit/check-generics.test Outdated
reveal_type(ts) # E: Revealed type is 'Any'

us = UA[str].x # E: "Type alias to Union" has no attribute "x"
us = UA.x # E: "Type alias to Union" has no attribute "x"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similar to above.

reveal_type(another) # E: Revealed type is 'y.G[y.G*[builtins.int]]'
reveal_type(second) # E: Revealed type is 'y.G[builtins.str*]'
reveal_type(last) # E: Revealed type is 'y.G[y.G*]'
reveal_type(last) # E: Revealed type is 'y.G[y.G*[Any]]'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks like there was a bug that you fixed here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Looks like there was a bug that you fixed here?

Yes, but it was unintentional :-)

[out]
==
c.py:2: error: Argument 1 has incompatible type "int"; expected "str"
c.py:2: error: Argument 1 to "A" has incompatible type "int"; expected "str"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, we can generate a better error message here.

@ilevkivskyi

Copy link
Copy Markdown
Member Author

Running testpr discovered few bugs here. Fixing them now.

@ilevkivskyi

Copy link
Copy Markdown
Member Author

@JukkaL Thanks for review! I have addressed all comments and fixed some bugs.
This is now ready for review again.


-- the order of errors is different with cache
[case testImportOnTopOfAlias2-skip-cache]
[case testImportOnTopOfAlias2]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good to have more test cases enabled.

@JukkaL JukkaL left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, thanks for all the fixes! Only one remaining comment. Feel free to merge once you've addressed it and once you've tested this with internal Dropbox codebases. This will greatly simplify future work with type aliases.

Comment thread mypy/checkexpr.py Outdated
if alias_definition:
return AnyType(TypeOfAny.special_form)
# This type is invalid in most runtime contexts.
kind = (' to Callable' if isinstance(item, CallableType) else

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The error message logic would better live in mypy.messages.

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.

3 participants