Skip to content
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

Merged

Conversation

Michael0x2a
Copy link
Collaborator

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 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.
@Michael0x2a Michael0x2a mentioned this pull request Dec 17, 2018
42 tasks
Copy link
Member

@ilevkivskyi ilevkivskyi left a 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's visit_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']'
Copy link
Member

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.

Michael0x2a added a commit to Michael0x2a/mypy that referenced this pull request Dec 19, 2018
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.
Michael0x2a added a commit that referenced this pull request Dec 28, 2018
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.
Michael0x2a added a commit to Michael0x2a/mypy that referenced this pull request Dec 31, 2018
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).
@Michael0x2a
Copy link
Collaborator Author

Note: I will rebase on top of master once #6121 lands.

@Michael0x2a
Copy link
Collaborator Author

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.

Copy link
Member

@ilevkivskyi ilevkivskyi left a 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.

"""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
Copy link
Member

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, ...

[out]
main:2: error: Revealed type is 'Literal[3]'
==
main:2: error: Revealed type is 'Literal[4]'
Copy link
Member

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.

@Michael0x2a Michael0x2a merged commit 2b8691d into python:master Jan 4, 2019
@Michael0x2a Michael0x2a deleted the add-incremental-and-fine-grained-tests branch January 4, 2019 18:30
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.

2 participants