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

Added conformance tests for new "Exceptions" chapter. #1724

Merged
merged 2 commits into from Apr 23, 2024

Conversation

erictraut
Copy link
Collaborator

No description provided.

@erictraut erictraut marked this pull request as ready for review April 22, 2024 21:32
with NoSuppress1():
raise ValueError("This exception is not suppressed")

return 1 # OK
Copy link
Member

Choose a reason for hiding this comment

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

I feel a type checker that recognizes that this code is unreachable could still reasonably show an error here, since the return type is definitely inconsistent with the annotation. Therefore, I'd prefer to use a different mechanism to test the desired semantics here.

This seems to work well:

from typing import Any, Literal, assert_type


class CMBase:
    def __enter__(self) -> None:
        pass


class Suppress1(CMBase):
    def __exit__(self, exc_type, exc_value, traceback) -> bool:
        return True


class Suppress2(CMBase):
    def __exit__(self, exc_type, exc_value, traceback) -> Literal[True]:
        return True


class NoSuppress1(CMBase):
    def __exit__(self, exc_type, exc_value, traceback) -> None:
        return None


def suppress1(x: int | str) -> None:
    if isinstance(x, int):
        with Suppress1():
            raise ValueError
    assert_type(x, int | str)


def nosuppress1(x: int | str) -> None:
    if isinstance(x, int):
        with NoSuppress1():
            raise ValueError
    assert_type(x, str)

pyright, mypy, pyre all pass.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, nice. Yeah, I was trying to think of other code flow patterns that might work better. I like your suggestion. I'll make that change.

Choose a reason for hiding this comment

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

Hmm... Pyre shouldn't pass this test, because I know for a fact that we get this wrong :)

In Pyre, we incorrectly assume that context managers never swallow exceptions. This stems from a fundamental design flaw, where we construct the control flow graph in a phase before we have any type information, so we can't possibly inspect the return type annotation.

Choose a reason for hiding this comment

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

Pyre passing in the playground seems to be an issue with the playground (I will investigate). When I try with a recent build in my dev environment, I see the expected failures for both suppress1 and suppress2.

Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `Union[int, str]` but got `str`.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@samwgoldman, hmm, I'm not sure how to square that with the test results. Pyre is emitting no errors (i.e. no assert_type failures) for this test, so it seems to be doing the right thing — unless there is some other bug that's causing it to not detect an assert_type mismatch. Any insights would be appreciated. If there's a better way to write this test, I'm happy to modify it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, interesting. The conformance tests make extensive use of assert_type. If assert_type is not working correctly, that will affect many test results.

Copy link
Member

Choose a reason for hiding this comment

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

Here's a variation that correctly finds the bug in pyre:

from typing import Any, Literal, assert_type


class CMBase:
    def __enter__(self) -> None:
        pass


class Suppress1(CMBase):
    def __exit__(self, exc_type, exc_value, traceback) -> bool:
        return True


class Suppress2(CMBase):
    def __exit__(self, exc_type, exc_value, traceback) -> Literal[True]:
        return True


class NoSuppress1(CMBase):
    def __exit__(self, exc_type, exc_value, traceback) -> None:
        return None


def suppress1(x: int | str) -> int | str:
    if isinstance(x, int):
        with Suppress1():
            raise ValueError
    return x + "x"  # E


def nosuppress1(x: int | str) -> str:
    if isinstance(x, int):
        with NoSuppress1():
            raise ValueError
    return x + "x"

https://pyre-check.org/play?input=from%20typing%20import%20Any%2C%20Literal%2C%20assert_type%0A%0A%0Aclass%20CMBase%3A%0A%20%20%20%20def%20__enter__(self)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20pass%0A%0A%0Aclass%20Suppress1(CMBase)%3A%0A%20%20%20%20def%20__exit__(self%2C%20exc_type%2C%20exc_value%2C%20traceback)%20-%3E%20bool%3A%0A%20%20%20%20%20%20%20%20return%20True%0A%0A%0Aclass%20Suppress2(CMBase)%3A%0A%20%20%20%20def%20__exit__(self%2C%20exc_type%2C%20exc_value%2C%20traceback)%20-%3E%20Literal%5BTrue%5D%3A%0A%20%20%20%20%20%20%20%20return%20True%0A%0A%0Aclass%20NoSuppress1(CMBase)%3A%0A%20%20%20%20def%20__exit__(self%2C%20exc_type%2C%20exc_value%2C%20traceback)%20-%3E%20None%3A%0A%20%20%20%20%20%20%20%20return%20None%0A%0A%0Adef%20suppress1(x%3A%20int%20%7C%20str)%20-%3E%20int%20%7C%20str%3A%0A%20%20%20%20if%20isinstance(x%2C%20int)%3A%0A%20%20%20%20%20%20%20%20with%20Suppress1()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%0A%20%20%20%20return%20x%20%2B%20%22x%22%20%20%23%20E%0A%0A%0Adef%20nosuppress1(x%3A%20int%20%7C%20str)%20-%3E%20str%3A%0A%20%20%20%20if%20isinstance(x%2C%20int)%3A%0A%20%20%20%20%20%20%20%20with%20NoSuppress1()%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20raise%20ValueError%0A%20%20%20%20return%20x%20%2B%20%22x%22%0A

It produces no errors, so it fails because it doesn't produce the expected error. Mypy and pyright produce the correct error.

Choose a reason for hiding this comment

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

Indeed, Steven fixed assert_type recently (a few weeks ago) in order to better support the conformance tests. I suspect that is the issue and we need to update the playground and re-record the conformance results.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Steven fixed assert_type recently

Ah, that's great to hear. If you publish a new version of pyre, the conformance test framework will automatically discover it and use it. Do you have a timeline for when you will publish the next version?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, that's great! We can use the x + y variant I posted above if necessary, but it would be better if we can use assert_type() here.

@erictraut erictraut merged commit f7f5723 into python:main Apr 23, 2024
5 checks passed
@erictraut erictraut deleted the exceptions_conformance branch April 23, 2024 00:37
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.

None yet

3 participants