Skip to content

Conversation

silamon
Copy link
Contributor

@silamon silamon commented Aug 2, 2025

Summary

Implements diagnostics for async context managers. Fixes astral-sh/ty#918.

Test Plan

Mdtests have been added.

@MichaReiser MichaReiser added ty Multi-file analysis & type inference diagnostics Related to reporting of diagnostics. labels Aug 2, 2025
Copy link
Contributor

github-actions bot commented Aug 2, 2025

Diagnostic diff on typing conformance tests

Changes were detected when running ty on typing conformance tests
--- old-output.txt	2025-08-05 06:55:05.191000775 +0000
+++ new-output.txt	2025-08-05 06:55:05.252000918 +0000
@@ -137,6 +137,7 @@
 callables_annotation.py:57:18: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `list[int]`?
 callables_annotation.py:58:5: error[invalid-type-form] Special form `typing.Callable` expected exactly two arguments (parameter types and return type)
 callables_annotation.py:58:14: error[invalid-type-form] The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`
+callables_annotation.py:114:14: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Protocol`
 callables_kwargs.py:24:5: error[type-assertion-failure] Argument does not have asserted type `int`
 callables_kwargs.py:32:9: error[type-assertion-failure] Argument does not have asserted type `str`
 callables_kwargs.py:35:5: error[type-assertion-failure] Argument does not have asserted type `str`
@@ -147,9 +148,12 @@
 callables_kwargs.py:65:5: error[missing-argument] No argument provided for required parameter `v3` of function `func2`
 callables_protocol.py:97:1: error[invalid-assignment] Object of type `def cb4_bad1(x: int) -> None` is not assignable to `Proto4`
 callables_protocol.py:121:1: error[invalid-assignment] Object of type `def cb6_bad1(*vals: bytes, *, max_len: int | None = None) -> list[bytes]` is not assignable to `NotProto6`
+callables_protocol.py:176:14: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Protocol`
 callables_protocol.py:179:62: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `R@__call__`
 callables_subtyping.py:26:5: error[invalid-assignment] Object of type `(int, /) -> int` is not assignable to `(int | float, /) -> int | float`
 callables_subtyping.py:29:5: error[invalid-assignment] Object of type `(int | float, /) -> int | float` is not assignable to `(int, /) -> int`
+callables_subtyping.py:204:21: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Protocol`
+classes_classvar.py:35:14: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`
 classes_classvar.py:38:11: error[invalid-type-form] Type qualifier `typing.ClassVar` expected exactly 1 argument, got 2
 classes_classvar.py:39:14: error[invalid-type-form] Int literals are not allowed in this context in a type expression
 classes_classvar.py:40:14: error[unresolved-reference] Name `var` used when not defined
@@ -382,6 +386,7 @@
 generics_defaults.py:55:1: error[type-assertion-failure] Argument does not have asserted type `@Todo`
 generics_defaults.py:59:1: error[type-assertion-failure] Argument does not have asserted type `@Todo`
 generics_defaults.py:63:1: error[type-assertion-failure] Argument does not have asserted type `@Todo`
+generics_defaults.py:76:23: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`
 generics_defaults.py:79:1: error[type-assertion-failure] Argument does not have asserted type `@Todo`
 generics_defaults.py:80:1: error[type-assertion-failure] Argument does not have asserted type `@Todo`
 generics_defaults.py:91:26: error[invalid-argument-type] `@Todo` is not a valid argument to `Generic`
@@ -412,6 +417,7 @@
 generics_paramspec_semantics.py:38:28: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
 generics_paramspec_semantics.py:53:34: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
 generics_paramspec_semantics.py:57:34: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
+generics_paramspec_semantics.py:67:9: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`
 generics_paramspec_semantics.py:76:30: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `str`
 generics_paramspec_semantics.py:82:5: error[type-assertion-failure] Argument does not have asserted type `@Todo`
 generics_paramspec_semantics.py:82:28: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `list[int]`?
@@ -424,9 +430,12 @@
 generics_paramspec_semantics.py:133:23: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
 generics_paramspec_semantics.py:138:29: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
 generics_paramspec_semantics.py:143:25: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
+generics_paramspec_specialization.py:13:14: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`
+generics_paramspec_specialization.py:18:29: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`
 generics_paramspec_specialization.py:32:27: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, bool]`?
 generics_paramspec_specialization.py:40:27: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?
 generics_paramspec_specialization.py:40:31: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?
+generics_paramspec_specialization.py:48:14: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Generic`
 generics_paramspec_specialization.py:52:22: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str, bool]`?
 generics_scoping.py:14:1: error[type-assertion-failure] Argument does not have asserted type `int`
 generics_scoping.py:15:1: error[type-assertion-failure] Argument does not have asserted type `str`
@@ -523,30 +532,23 @@
 generics_type_erasure.py:40:16: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `str | None`, found `Literal[0]`
 generics_type_erasure.py:47:1: error[type-assertion-failure] Argument does not have asserted type `int`
 generics_type_erasure.py:56:1: error[type-assertion-failure] Argument does not have asserted type `bytes`
-generics_typevartuple_args.py:16:34: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo, ...]`
 generics_typevartuple_args.py:20:1: error[type-assertion-failure] Argument does not have asserted type `tuple[int, str]`
-generics_typevartuple_args.py:27:77: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo, ...]`
 generics_typevartuple_args.py:31:1: error[type-assertion-failure] Argument does not have asserted type `tuple[()]`
 generics_typevartuple_args.py:32:1: error[type-assertion-failure] Argument does not have asserted type `tuple[int, str]`
 generics_typevartuple_basic.py:12:14: error[invalid-argument-type] `@Todo` is not a valid argument to `Generic`
-generics_typevartuple_basic.py:16:26: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo, ...]`
 generics_typevartuple_basic.py:23:13: error[invalid-argument-type] `@Todo` is not a valid argument to `Generic`
-generics_typevartuple_basic.py:42:34: error[invalid-argument-type] Argument to bound method `__init__` is incorrect: Expected `tuple[@Todo, ...]`, found `Literal[1]`
+generics_typevartuple_basic.py:52:14: error[invalid-argument-type] `TypeVarTuple` is not a valid argument to `Generic`
 generics_typevartuple_basic.py:65:27: error[unknown-argument] Argument `covariant` does not match any known parameter of function `__new__`
 generics_typevartuple_basic.py:66:27: error[too-many-positional-arguments] Too many positional arguments to function `__new__`: expected 2, got 4
 generics_typevartuple_basic.py:67:27: error[unknown-argument] Argument `bound` does not match any known parameter of function `__new__`
-generics_typevartuple_basic.py:75:50: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo, ...]`
 generics_typevartuple_basic.py:84:1: error[type-assertion-failure] Argument does not have asserted type `tuple[int]`
 generics_typevartuple_basic.py:106:14: error[invalid-argument-type] `@Todo` is not a valid argument to `Generic`
-generics_typevartuple_callable.py:29:57: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo, ...]`
 generics_typevartuple_callable.py:33:54: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[int | float | complex, str, int]`
 generics_typevartuple_callable.py:37:34: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[str]`
 generics_typevartuple_callable.py:41:1: error[type-assertion-failure] Argument does not have asserted type `tuple[str, int, int | float | complex]`
 generics_typevartuple_callable.py:42:1: error[type-assertion-failure] Argument does not have asserted type `tuple[str]`
-generics_typevartuple_callable.py:45:43: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo, ...]`
 generics_typevartuple_callable.py:49:1: error[type-assertion-failure] Argument does not have asserted type `tuple[int | float, str, int | float | complex]`
 generics_typevartuple_concat.py:22:13: error[invalid-argument-type] `@Todo` is not a valid argument to `Generic`
-generics_typevartuple_concat.py:47:42: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo, ...]`
 generics_typevartuple_concat.py:52:1: error[type-assertion-failure] Argument does not have asserted type `tuple[int, bool, str]`
 generics_typevartuple_overloads.py:16:13: error[invalid-argument-type] `@Todo` is not a valid argument to `Generic`
 generics_typevartuple_specialization.py:16:13: error[invalid-argument-type] `@Todo` is not a valid argument to `Generic`
@@ -558,14 +560,15 @@
 generics_typevartuple_specialization.py:52:5: error[type-assertion-failure] Argument does not have asserted type `tuple[str, @Todo]`
 generics_typevartuple_specialization.py:52:37: error[invalid-type-form] Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?
 generics_typevartuple_specialization.py:59:14: error[invalid-argument-type] `@Todo` is not a valid argument to `Generic`
+generics_typevartuple_specialization.py:92:67: error[invalid-type-form] Variable of type `type[@Todo]` is not allowed in a type expression
 generics_typevartuple_specialization.py:93:5: error[type-assertion-failure] Argument does not have asserted type `tuple[str, int]`
 generics_typevartuple_specialization.py:94:5: error[type-assertion-failure] Argument does not have asserted type `tuple[int | float]`
 generics_typevartuple_specialization.py:95:5: error[type-assertion-failure] Argument does not have asserted type `tuple[Any, *tuple[Any, ...]]`
-generics_typevartuple_specialization.py:130:35: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[tuple[@Todo, ...], T1@func7, T2@func7]`
+generics_typevartuple_specialization.py:130:35: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo, T1@func7, T2@func7]`
 generics_typevartuple_specialization.py:135:5: error[type-assertion-failure] Argument does not have asserted type `tuple[tuple[()], str, bool]`
 generics_typevartuple_specialization.py:136:5: error[type-assertion-failure] Argument does not have asserted type `tuple[tuple[str], bool, int | float]`
 generics_typevartuple_specialization.py:137:5: error[type-assertion-failure] Argument does not have asserted type `tuple[tuple[str, bool], int | float, int]`
-generics_typevartuple_specialization.py:143:39: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[tuple[@Todo, ...], T1@func9, T2@func9, T3@func9]`
+generics_typevartuple_specialization.py:143:39: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `tuple[@Todo, T1@func9, T2@func9, T3@func9]`
 generics_typevartuple_specialization.py:148:5: error[type-assertion-failure] Argument does not have asserted type `tuple[tuple[()], str, bool, int | float]`
 generics_typevartuple_specialization.py:149:5: error[type-assertion-failure] Argument does not have asserted type `tuple[tuple[bool], str, int | float, int]`
 generics_typevartuple_specialization.py:157:5: error[type-assertion-failure] Argument does not have asserted type `tuple[*tuple[int, ...], int]`
@@ -784,6 +787,7 @@
 protocols_subtyping.py:16:6: error[call-non-callable] Cannot instantiate class `Proto1`: This call will raise `TypeError` at runtime
 protocols_subtyping.py:38:5: error[invalid-assignment] Object of type `Proto2` is not assignable to `Concrete2`
 protocols_subtyping.py:55:5: error[invalid-assignment] Object of type `Proto2` is not assignable to `Proto3`
+protocols_variance.py:84:16: error[invalid-argument-type] `ParamSpec` is not a valid argument to `Protocol`
 protocols_variance.py:85:62: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `R@__call__`
 qualifiers_annotated.py:43:17: error[invalid-type-form] List literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?
 qualifiers_annotated.py:44:17: error[invalid-type-form] Tuple literals are not allowed in this context in a type expression
@@ -885,4 +889,4 @@
 tuples_type_form.py:36:1: error[invalid-assignment] Object of type `tuple[Literal[1], Literal[2], Literal[3], Literal[""]]` is not assignable to `tuple[int, ...]`
 typeddicts_operations.py:60:1: error[type-assertion-failure] Argument does not have asserted type `str | None`
 typeddicts_type_consistency.py:101:1: error[invalid-assignment] Object of type `Unknown | None` is not assignable to `str`
-Found 886 diagnostics
+Found 890 diagnostics

Copy link
Contributor

github-actions bot commented Aug 2, 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:40: error[invalid-assignment] Object of type `CoroutineType[Any, Any, Unknown]` is not assignable to `PrefectClient | None`
+ src/prefect/artifacts.py:689:15: warning[possibly-unbound-attribute] Attribute `update_artifact` on type `PrefectClient | None` is possibly unbound
- Found 3838 diagnostics
+ Found 3841 diagnostics
No memory usage changes detected ✅

Copy link
Contributor

@sharkdp sharkdp left a comment

Choose a reason for hiding this comment

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

Thank you very much — this is great!

Only two minor comments. And we should probably take a look at the three new diagnostics in the ecosystem (see mypy_primer results PR comment)

@sharkdp sharkdp self-assigned this Aug 4, 2025
@silamon
Copy link
Contributor Author

silamon commented Aug 4, 2025

Everything CI-wise should be expected to pass right now.

@carljm
Copy link
Contributor

carljm commented Aug 5, 2025

Looks like tests are still failing due to a stray .snap file that isn't referenced by any test -- that needs to be removed.

@carljm
Copy link
Contributor

carljm commented Aug 5, 2025

This looks good to me, merging.

I reviewed the ecosystem hit and it looks like a true positive: nullcontext in Python 3.9 (which is the minimum supported version in the prefect pyproject.toml) does not implement __aenter__ or __aexit__. Switching to asyncnullcontext (also imported in that file) fixes the diagnostic. (The other two diagnostics are just cascading impact of not getting an accurate return type from __aenter__; if we want to avoid that, maybe we should infer simply Unknown instead of CoroutineType[Any, Any, Unknown] from a failed __aenter__ call, but that seems like a separate issue.)

The other issue I notice from looking at this ecosystem hit is that our diagnostics for failed dunder calls on union types could be better, but that issue pre-exists this PR and doesn't need to be fixed here; I filed astral-sh/ty#940 to track that.

@carljm carljm merged commit 934fd37 into astral-sh:main Aug 5, 2025
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
diagnostics Related to reporting of diagnostics. ty Multi-file analysis & type inference
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Diagnostics for async context managers
4 participants