Skip to content

Conversation

Y3K
Copy link

@Y3K Y3K commented Sep 1, 2025

Summary

Fixes astral-sh/ty#940

Test Plan

class Bound:
    def __enter__(self): ...

    def __exit__(self, *args): ...


class EnterUnbound:
    def __exit__(self): ...


class ExitUnbound:
    def __enter__(self): ...


class Unbound: ...


def bound(x: Bound):
    with x: ...


def enter_unbound(x: EnterUnbound):
    with x: ...


def exit_unbound(x: ExitUnbound):
    with x: ...


def unbound(x: Unbound):
    with x: ...


def union_enter_unbound(x: Bound | EnterUnbound):
    with x: ...


def union_exit_unbound(x: Bound | ExitUnbound):
    with x: ...


def union_unbound(x: Bound | Unbound):
    with x: ...


def multiple(x: Bound | EnterUnbound | ExitUnbound):
    with x: ...
$ cargo run --bin ty -- check --project ../pydummy

error[invalid-context-manager]: Object of type `EnterUnbound` cannot be used with `with` because it does not implement `__enter__`, and it does not correctly implement `__exit__`
  --> ../pydummy/main.py:23:10
   |
22 | def enter_unbound(x: EnterUnbound):
23 |     with x: ...
   |          ^
   |
info: rule `invalid-context-manager` is enabled by default

error[invalid-context-manager]: Object of type `ExitUnbound` cannot be used with `with` because it does not implement `__exit__`
  --> ../pydummy/main.py:27:10
   |
26 | def exit_unbound(x: ExitUnbound):
27 |     with x: ...
   |          ^
   |
info: rule `invalid-context-manager` is enabled by default

error[invalid-context-manager]: Object of type `Unbound` cannot be used with `with` because it does not implement `__enter__` and `__exit__`
  --> ../pydummy/main.py:31:10
   |
30 | def unbound(x: Unbound):
31 |     with x: ...
   |          ^
   |
info: rule `invalid-context-manager` is enabled by default

error[invalid-context-manager]: Object of type `Bound | EnterUnbound` cannot be used with `with` because the method `__enter__` of `EnterUnbound` is possibly unbound
  --> ../pydummy/main.py:35:10
   |
34 | def union_enter_unbound(x: Bound | EnterUnbound):
35 |     with x: ...
   |          ^
   |
info: rule `invalid-context-manager` is enabled by default

error[invalid-context-manager]: Object of type `Bound | ExitUnbound` cannot be used with `with` because the method `__exit__` of `ExitUnbound` is possibly unbound
  --> ../pydummy/main.py:39:10
   |
38 | def union_exit_unbound(x: Bound | ExitUnbound):
39 |     with x: ...
   |          ^
   |
info: rule `invalid-context-manager` is enabled by default

error[invalid-context-manager]: Object of type `Bound | Unbound` cannot be used with `with` because the methods `__enter__` and `__exit__` of `Unbound` are possibly unbound
  --> ../pydummy/main.py:43:10
   |
42 | def union_unbound(x: Bound | Unbound):
43 |     with x: ...
   |          ^
   |
info: rule `invalid-context-manager` is enabled by default

error[invalid-context-manager]: Object of type `Bound | EnterUnbound | ExitUnbound` cannot be used with `with` because the method `__enter__` of `EnterUnbound` is possibly unbound, and the method `__exit__` of `ExitUnbound` is possibly unbound
  --> ../pydummy/main.py:47:10
   |
46 | def multiple(x: Bound | EnterUnbound | ExitUnbound):
47 |     with x: ...
   |          ^
   |
info: rule `invalid-context-manager` is enabled by default

Copy link
Contributor

github-actions bot commented Sep 1, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

Copy link
Contributor

github-actions bot commented Sep 1, 2025

mypy_primer results

Changes were detected when running on open source projects
prefect (https://github.com/PrefectHQ/prefect)
- src/prefect/artifacts.py:675:16: error[invalid-context-manager] Object of type `nullcontext[PrefectClient & ~AlwaysFalsy] | PrefectClient` cannot be used with `async with` because the methods `__aenter__` and `__aexit__` are possibly unbound
+ src/prefect/artifacts.py:675:16: error[invalid-context-manager] Object of type `nullcontext[PrefectClient & ~AlwaysFalsy] | PrefectClient` cannot be used with `async with` because the methods `__aenter__` and `__aexit__` of `nullcontext[PrefectClient & ~AlwaysFalsy]` are possibly unbound

xarray (https://github.com/pydata/xarray)
- xarray/tests/test_distributed.py:316:14: error[invalid-context-manager] Object of type `Unknown | None` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ xarray/tests/test_distributed.py:316:14: error[invalid-context-manager] Object of type `Unknown | None` cannot be used with `with` because the methods `__enter__` and `__exit__` of `None` are possibly unbound

pandas (https://github.com/pandas-dev/pandas)
- pandas/tests/io/test_stata.py:1302:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ pandas/tests/io/test_stata.py:1302:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` of `DataFrame` are possibly unbound
- pandas/tests/io/test_stata.py:1347:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ pandas/tests/io/test_stata.py:1347:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` of `DataFrame` are possibly unbound
- pandas/tests/io/test_stata.py:1351:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ pandas/tests/io/test_stata.py:1351:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` of `DataFrame` are possibly unbound
- pandas/tests/io/test_stata.py:1355:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ pandas/tests/io/test_stata.py:1355:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` of `DataFrame` are possibly unbound
- pandas/tests/io/test_stata.py:1359:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ pandas/tests/io/test_stata.py:1359:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` of `DataFrame` are possibly unbound
- pandas/tests/io/test_stata.py:1364:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ pandas/tests/io/test_stata.py:1364:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` of `DataFrame` are possibly unbound
- pandas/tests/io/test_stata.py:1400:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ pandas/tests/io/test_stata.py:1400:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` of `DataFrame` are possibly unbound
- pandas/tests/io/test_stata.py:1427:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ pandas/tests/io/test_stata.py:1427:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` of `DataFrame` are possibly unbound
- pandas/tests/io/test_stata.py:1692:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ pandas/tests/io/test_stata.py:1692:14: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` of `DataFrame` are possibly unbound
- pandas/tests/io/test_stata.py:2351:10: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` are possibly unbound
+ pandas/tests/io/test_stata.py:2351:10: error[invalid-context-manager] Object of type `DataFrame | StataReader` cannot be used with `with` because the methods `__enter__` and `__exit__` of `DataFrame` are possibly unbound
No memory usage changes detected ✅

@Y3K Y3K force-pushed the chore/ContextManageError_message_update branch from 06686e9 to 0fdf81e Compare September 1, 2025 19:54
@Y3K Y3K changed the title [ty] chore: ContextManagerError message update [ty] chore: ContextManagerError message update Sep 1, 2025
@Y3K Y3K force-pushed the chore/ContextManageError_message_update branch from 0fdf81e to 9733d41 Compare September 1, 2025 21:49
@ntBre ntBre added the ty Multi-file analysis & type inference label Sep 2, 2025
Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

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

Thanks for the pull request! This is definitely an improvement.

My idea when I filed the issue was that we would bubble up the information about which types had the dunder unbound or possibly-unbound as part of the CallDunderError, but looking at it now I see this would probably require special-casing Type::Union in try_call_dunder (because member_lookup_with_policy squashes union information into a single PlaceAndQualifiers). I still think this might make sense, but I also think it's OK to just re-create the information once we know there's a problem.

@@ -84,6 +84,117 @@ async def main():
...
```

## Union context managers with specific member issues

Copy link
Contributor

Choose a reason for hiding this comment

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

We can snapshot diagnostics for these tests, which is an easier way to see the full resulting diagnostic, instead of putting full error messages directly in the mdtest. This is usually preferred for tests focused on diagnostic rendering, and is especially useful when the diagnostic is not all crammed into a single sentence, as I suggest below.

Suggested change
<!-- snapshot-diagnostics -->

Then you use cargo insta test -p ty_python_semantic to generate new snapshots to review with cargo insta review and then commit.

Copy link
Author

Choose a reason for hiding this comment

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

Sorry for the rookie question.

Can I get a really brief example of how snapshots work?

e.g. should be snapshotted in main or post changes? What if the message changes do I need to update snaps? Are all the snaps run when I do cargo nextest run?


context_expr = Bound() if flag else EnterUnbound()

# error: [invalid-context-manager] "Object of type `Bound | EnterUnbound` cannot be used with `async with` because the method `__aenter__` of `EnterUnbound` is possibly unbound"
Copy link
Contributor

Choose a reason for hiding this comment

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

E.g. if we snapshot diagnostics, then we can just do this here:

Suggested change
# error: [invalid-context-manager] "Object of type `Bound | EnterUnbound` cannot be used with `async with` because the method `__aenter__` of `EnterUnbound` is possibly unbound"
# error: [invalid-context-manager]

@sharkdp sharkdp removed their request for review September 3, 2025 07:22
@Y3K
Copy link
Author

Y3K commented Sep 3, 2025

Thank you for your review @carljm!

I'm quite rookie with Rust and ty therefore the changes requested still are a bit confussing to me.

I'll read each suggestion carefully and work on a new approach, will ping you once it's ready.

Thanks!

@carljm
Copy link
Contributor

carljm commented Sep 3, 2025

@Y3K happy to offer clarification on any comments. I think for purposes of this PR you can ignore the comment about "bubbling up the information through CallDunderError", and you can ignore one other comment I'll mention inline. The other ones I think don't require an entirely "new approach" to address, just some updates to the current approach.

@Y3K
Copy link
Author

Y3K commented Sep 3, 2025

Thank you again, @carljm !

I don't want to waste a lot of your time, but I do accept any extra help you want and can provide.

Will address all your feedback within this PR 🙌🏼

@Y3K Y3K marked this pull request as draft September 3, 2025 17:33
@Y3K Y3K force-pushed the chore/ContextManageError_message_update branch from aee5519 to 67846c0 Compare September 3, 2025 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ty Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

improve diagnostics for possibly-unbound call-dunder with union
3 participants