-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Fix crashes and fails in forward references #3952
Conversation
An important comment, for some reason forward references between files still don't work properly (but at least they don't crash). |
This is ready for review, but I marked it WIP since I have found one more crash, will fix it shortly. |
…; Better error in case something is still missing
OK, I have fixed some remaining problems and TODO's, also added more tests. |
Thank you very much for implementing this! Based on a quick pass, it looks like this fixes many long-standing issues in a clean way. Forward reference handling will be also be useful for fine-grained incremental checking, which we are planning to continue working on later this year. Special thanks for writing many test cases. I'll try to do a full review by mid next week. I'll also run this against internal Dropbox codebases to see if there are regressions (or performance issues). Also, there's now a merge conflict. Can you fix it? |
Fixed.
I am glad you like the tests. But this is a subtle area, if you have ideas for more tests, then I will be grateful (I am sure this PR does not fix all issues with forward references, for example across files as I mentioned above, but this could be a good start). |
I ran this against internal Dropbox repos and found one minor difference. Previously this code didn't generate an error: from typing import AnyStr, Dict
def f(): # Note no annotation
x = {} # type: Dict[str, AnyStr] Now it generates this error:
Also I there seems to be no significant performance impact, which is good. I'll continue tomorrow with a more detailed review. |
@JukkaL I started addressing your comments, I will try to finish before the end of this week. While making changes you requested, I remembered something important I found before: third pass calls |
…ly lookup functions
Looks like travis flaked. I restarted that job. |
@JukkaL I think I have now addressed all your comments. This is now ready for further review. |
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.
Thanks for the updates! This seems pretty close to ready. I left a bunch of minor comments.
test-data/unit/check-namedtuple.test
Outdated
class G(Generic[T]): | ||
x: T | ||
|
||
yb: G[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.
Shouldn't this be rejected, since int
is not compatible with M
?
test-data/unit/check-namedtuple.test
Outdated
yb: G[int] | ||
yg: G[M] | ||
z: int = G[M]().x.x | ||
z = G[M]().x[0] |
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.
reveal_type
would be better here as well.
test-data/unit/check-statements.test
Outdated
lst: List[N] | ||
|
||
for i in lst: # type: N | ||
a: int = i.x |
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.
Again, prefer reveal_type
.
test-data/unit/check-statements.test
Outdated
cm: ContextManager[N] | ||
|
||
with cm as g: # type: N | ||
a: str = g['x'] |
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.
reveal_type
is better here as well.
test-data/unit/check-typeddict.test
Outdated
class G(Generic[T]): | ||
x: T | ||
|
||
yb: G[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 to above, shouldn't this be rejected?
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 rejected, here and above. I see the problem, it looks like you reviewed only a commit range, not all changes in PR.
I will anyway go though all comments this evening. Thanks for review!
mypy/semanal.py
Outdated
@@ -4275,6 +4291,15 @@ def perform_transform(self, node: Union[Node, SymbolTableNode], | |||
new_bases.append(alt_base) | |||
node.bases = new_bases | |||
|
|||
def transform_types(self, lvalue: Lvalue, transform: Callable[[Type], 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.
The name of the method would be more informative as transform_types_in_lvalue
or similar.
mypy/typeanal.py
Outdated
bound = tvar.upper_bound | ||
if isinstance(bound, ForwardRef): | ||
bound = bound.link | ||
if isinstance(bound, Instance) and bound.type.replaced: |
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 code is almost duplicated somewhere else. If so, it would be better to have only one implementation in a utility function/method.
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 will minimize duplication (but I can't use exactly the code from visitor in semanal.py
it is too soon here).
test-data/unit/check-namedtuple.test
Outdated
@@ -440,7 +440,7 @@ T = TypeVar('T', bound='M') | |||
class G(Generic[T]): | |||
x: T | |||
|
|||
yb: G[int] | |||
yb: G[int] # E: Type argument "builtins.int" of "G" must be a subtype of "Tuple[builtins.int, fallback=__main__.M]" | |||
yg: G[M] | |||
z: int = G[M]().x.x |
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.
Using reveal_type
would be more reliable as it would catch unwanted Any
types.
mypy/semanal.py
Outdated
@@ -4349,8 +4355,14 @@ def analyze_types(self, types: List[Type], node: Node) -> None: | |||
# Similar to above but for nodes with multiple types. | |||
indicator = {} # type: Dict[str, bool] | |||
for type in types: | |||
analyzer = TypeAnalyserPass3(self.fail, self.options, self.is_typeshed_file, | |||
self.sem, indicator) | |||
analyzer = TypeAnalyserPass3(self.sem.lookup_qualified, |
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 combine the two places where we generate TypeAnalyserPass3
as there seems to be duplication?
mypy/types.py
Outdated
x: A | ||
A = TypedDict('A', {'x': int}) | ||
|
||
To avoid false positives and crashes in such situations, we first wrap the second |
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.
Should this be 'the first occurrence ...'?
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, will fix this docstring.
Most of my comments from the last round are shown as outdated because I screwed up things a little. I think that they should mostly be still relevant though. |
By last round you mean few minutes ago ago or few days ago? I see almost everything as outdated, I assume you mean few minutes ago, I received an e-mail from GitHub, so that I can see these comments. |
@JukkaL I think I now implemented all your recent comments. In addition I fixed one more minor crash on MRO being
|
Thanks for the updates! Glad to see so many bugs fixed. |
Fixes #3340
Fixes #3419
Fixes #3674
Fixes #3685
Fixes #3799
Fixes #3836
Fixes #3881
Fixes #867
Fixes #2241
Fixes #2399
Fixes #1701
Fixes #3016
Fixes #3054
Fixes #2762
Fixes #3575
Fixes #3990
Currently, forward references don't work with anything apart from classes, for example this doesn't work:
The same situation is with
TypedDict
s,NewType
s, and type aliases. The root problem is that these synthetic types are neither detected in first pass, nor fixed in third pass. In certain cases this can lead to crashes (first six issues above are various crash scenarios). I fix these crashes by applying some additional patches after third pass. Here is the summary of the PR:ForwardRef
with only one fieldlink
is introduced (with updates to type visitors)UnboundType
is wrapped inForwardRef
, it is given a "second chance" in third pass.TypeReplacer
(which is the core of this PR).Here are two problems that I encountered:
semanal.py
and intypeanal.py
) was more "shallow" than the second one, some visitor methods were literallypass
. It was necessary to update these to match the "depth" of the second pass.self.sem
, same as for first pass. It would be nice to refactor the passes since all three share some code/functionality, there is already Refactor and document semantic analysis passes #3459 to track this.NOTE: self-referential types are still not properly supported, but now we give a reasonable error for this, not a crash, and they still can be used to certain extent, for example:
results in
Proper support of recursive types would be much harder (mostly due to (de-)serialization that would require something similar to
type_ref
), there is a separate issue for this #731.@JukkaL sorry it took a bit longer than expected because of CPython sprint last week.