-
-
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
Add tests for Literal types with incremental and fine-grained mode #6075
Add tests for Literal types with incremental and fine-grained mode #6075
Conversation
This pull request adds a variety of tests for Literal types and incremental and fine-grained mode. Many of these tests are a bit perfunctory: literal types don't really interact with incremental/fine-grained logic in a special way, so it was a little hard for me to think of good test cases. The only code-related change I ended up needing to make was to remove the assert from the mypy.server.astmerge.TypeReplaceVisitor's `visit_raw_literal_type` method. This actually worries me a bit: we should have removed all instances of RawLiteralType after phase 2 of semantic analysis. Does the merge step happen before then? If the merge is supposed to happen after phase 2, I can spend more time trying to track down the leak and update 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.
The only code-related change I ended up needing to make was to remove the assert from the
mypy.server.astmerge.TypeReplaceVisitor
'svisit_raw_literal_type
method.
If you remember, this is one of the danger I mentioned when we discussed RawLiteralType
few weeks ago. As we agreed at that time, we should use the hard asserts, even though this may cause some crashes initially. There should be no raw types left at the stage where the merge is performed, so it is better to track the cause of the assert.
[out] | ||
main:2: error: Revealed type is 'builtins.str' | ||
== | ||
main:2: error: Revealed type is 'Literal['foo']' |
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.
These tests are indeed not very useful. What might be more interesting is to test the situations with "chained" assignments (where you have y = x
, z = y
, w = z
etc, in different targets/modules) and where the type of the initial variable is changed, that causes a type error to appear with the last variable.
A similar idea is when you have a chain of function aliases where the last one provides a context for a function call. You can try playing with this.
Also maybe more interesting tests can be added when you will implement intelligent indexing and interaction with Final
.
This pull request adds support for byte and unicode Literal strings. I left in some comments explaining some nuances of the implementation; here are a few additional meta-notes: 1. I reworded several of the comments suggesting that the way we represent bytes as a string is a "hack" or that we should eventually switch to representing bytes as literally bytes. I started with that approach but ultimately rejected it: I ended up having to constantly serialize/deserialize between bytes and strings, which I felt complicated the code. As a result, I decided that the solution we had previously is in fact, from a high-level perspective, the best possible approach. (The actual code for translating the output of `typed_ast` into a human-readable string *is* admittedly a bit hacky though.) In any case, the phrase "how mypy currently parses the contents of bytes literals" is severely out-of-date anyways. That comment was added about 3 years ago, when we were adding the fast parser for the first time and running it concurrently with the actual parser. 2. I removed the `is_stub` field from `fastparse2.ASTConverter`: it turned out we were just never using that field. 3. One complication I ran into was figuring out how to handle forward references to literal strings. For example, suppose we have the type `List["Literal['foo']"]`. Do we treat this as being equivalent to `List[Literal[u'foo']]` or `List[Literal[b'foo']]`? If this is a Python 3 file or a Python 2 file with `unicode_literals`, we'd want to pick the former. If this is a standard Python 2 file, we'd want to pick the latter. In order to make this happen, I decided to use a heuristic where the type of the "outer" string decides the type of the "inner" string. For example: - In Python 3, `"Literal['foo']"` is a unicode string. So, the inner `Literal['foo']` will be treated as the same as `Literal[u'foo']`. - The same thing happens when using Python 2 with `unicode_literals`. - In Python 3, it is illegal to use a byte string as a forward reference. So, types like `List[b"Literal['foo']"]` are already illegal. - In standard Python 2, `"Literal['foo']"` is a byte string. So the inner `Literal['foo']` will be treated as the same as `Literal[u'foo']`. 4. I will add tests validating that all of this stuff works as expected with incremental and fine-grained mode in a separate diff -- probably after fixing and landing python#6075, which I intend to use as a baseline foundation.
This pull request adds support for byte and unicode Literal strings. I left in some comments explaining some nuances of the implementation; here are a few additional meta-notes: 1. I reworded several of the comments suggesting that the way we represent bytes as a string is a "hack" or that we should eventually switch to representing bytes as literally bytes. I started with that approach but ultimately rejected it: I ended up having to constantly serialize/deserialize between bytes and strings, which I felt complicated the code. As a result, I decided that the solution we had previously is in fact, from a high-level perspective, the best possible approach. (The actual code for translating the output of `typed_ast` into a human-readable string *is* admittedly a bit hacky though.) In any case, the phrase "how mypy currently parses the contents of bytes literals" is severely out-of-date anyways. That comment was added about 3 years ago, when we were adding the fast parser for the first time and running it concurrently with the actual parser. 2. I removed the `is_stub` field from `fastparse2.ASTConverter`: it turned out we were just never using that field. 3. One complication I ran into was figuring out how to handle forward references to literal strings. For example, suppose we have the type `List["Literal['foo']"]`. Do we treat this as being equivalent to `List[Literal[u'foo']]` or `List[Literal[b'foo']]`? If this is a Python 3 file or a Python 2 file with `unicode_literals`, we'd want to pick the former. If this is a standard Python 2 file, we'd want to pick the latter. In order to make this happen, I decided to use a heuristic where the type of the "outer" string decides the type of the "inner" string. For example: - In Python 3, `"Literal['foo']"` is a unicode string. So, the inner `Literal['foo']` will be treated as the same as `Literal[u'foo']`. - The same thing happens when using Python 2 with `unicode_literals`. - In Python 3, it is illegal to use a byte string as a forward reference. So, types like `List[b"Literal['foo']"]` are already illegal. - In standard Python 2, `"Literal['foo']"` is a byte string. So the inner `Literal['foo']` will be treated as the same as `Literal[u'foo']`. 4. I will add tests validating that all of this stuff works as expected with incremental and fine-grained mode in a separate diff -- probably after fixing and landing #6075, which I intend to use as a baseline foundation.
This pull request adds a variety of tests for Literal types and incremental and fine-grained mode. Many of these tests are a bit perfunctory: literal types don't really interact with incremental/fine-grained logic in a special way, so it was a little hard for me to think of good test cases. The only code-related change I ended up needing to make was to remove the assert from the mypy.server.astmerge.TypeReplaceVisitor's `visit_raw_literal_type` method. This actually worries me a bit: we should have removed all instances of RawLiteralType after phase 2 of semantic analysis. Does the merge step happen before then? If the merge is supposed to happen after phase 2, I can spend more time trying to track down the leak and update this PR.
This diff changes how we track raw literal types in the semantic analysis phase. It makes the following changes: 1. Removes the `RawLiteralType` synthetic type. 2. Adds a new `TypeOfAny`: `TypeOfAny.invalid_type` as suggested in python#4030. 3. Modifies `AnyType` so it can optionally contain a new `RawLiteral` class. This class contains information about the underlying literal that produced that particular `TypeOfAny`. 4. Adjusts mypy to stop recommending using `Literal[...]` when doing `A = NewType('A', 4)` or `T = TypeVar('T', bound=4)`. (The former suggestion is a bad one: you can't create a NewType of a Literal[...] type. The latter suggestion is a valid but stupid one: `T = TypeVar('T', bound=Literal[4])` is basically the same thing as `T = Literal[4]`.) This resolves python#5989. The net effect of this diff is that: 1. RawLiteralTypes no longer leak during fine-grained mode, which should partially help unblock python#6075. 2. The way mypy handles literal expressions in types is "inverted". Previously, we by default assumed literal expressions would belong inside `Literal[...]` and tacked on some logic to make them convert into error `AnyTypes`. Now, we do the reverse: we start with an error `AnyType` and convert those into `Literal[...]`s as needed. This more closely mirrors the way mypy *used* to work before we started work on Literal types. It should also hopefully help reduce some of the cognitive burden of working on other parts of the semantic analysis code, since we no longer need to worry about the `RawLiteralType` synthetic type. 3. We now have more flexibility in how we choose to handle invalid types: since they're just `Anys`, we have more opportunities to intercept and customize the exact way in which we handle errors. Also see python#4030 for additional context. (This diff lays out some of the foundation work for that diff).
Note: I will rebase on top of master once #6121 lands. |
# Conflicts: # test-data/unit/fine-grained.test
…ichael0x2a/mypy into add-incremental-and-fine-grained-tests
As an update, I've added a few more tests: mostly some things testing and string/bytes/unicode stuff. I'll continue adding more tomorrow morning, or in a separate PR if you want. If we decide to land this mostly as-is, I'll close #6121. Or, we could land some form of that PR and I can rebase on top of it. |
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 am fine with landing this as is. Here are two optional extra comments.
mypy/server/astmerge.py
Outdated
"""Similar to NodeReplaceVisitor, but for type objects. | ||
|
||
Note: this class may sometimes be used to analyze types before | ||
semantic analysis has taken place. For example, wee |
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 would reformulate this as this visitor may sometimes visit unanalyzed types such as 'UnboundType' and 'RawLiteralType'. For example, ...
test-data/unit/fine-grained.test
Outdated
[out] | ||
main:2: error: Revealed type is 'Literal[3]' | ||
== | ||
main:2: error: Revealed type is 'Literal[4]' |
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.
As you know, errors can cause an extra pass for a target, so I would remove the reveal type errors on the first run in the tests where it is obvious.
This pull request adds a variety of tests for Literal types and incremental and fine-grained mode.
Many of these tests are a bit perfunctory: literal types don't really interact with incremental/fine-grained logic in a special way, so it was a little hard for me to think of good test cases.
The only code-related change I ended up needing to make was to remove the assert from the
mypy.server.astmerge.TypeReplaceVisitor
'svisit_raw_literal_type
method.This actually worries me a bit: we should have removed all instances of RawLiteralType after phase 2 of semantic analysis. Does the merge step happen before then? If the merge is supposed to happen after phase 2, I can spend more time trying to track down the leak and update this PR.