diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 3db6a2b32b..9aadce2eca 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -36,7 +36,7 @@ def test(): -> "int": Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L101) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L102) **What it does** @@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L145) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L146) **What it does** @@ -88,7 +88,7 @@ f(int) # error Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L171) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L172) **What it does** @@ -117,7 +117,7 @@ a = 1 Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L196) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L197) **What it does** @@ -147,7 +147,7 @@ class C(A, B): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L222) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L223) **What it does** @@ -177,7 +177,7 @@ class B(A): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L287) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L288) **What it does** @@ -202,7 +202,7 @@ class B(A, A): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L308) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L309) **What it does** @@ -306,7 +306,7 @@ def test(): -> "Literal[5]": Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L450) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L451) **What it does** @@ -334,7 +334,7 @@ class C(A, B): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L474) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L475) **What it does** @@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L340) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L341) **What it does** @@ -445,7 +445,7 @@ an atypical memory layout. Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L519) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L520) **What it does** @@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type] Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L559) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L560) **What it does** @@ -496,7 +496,7 @@ a: int = '' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1563) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1594) **What it does** @@ -523,12 +523,46 @@ C().instance_var = 3 # okay C.instance_var = 3 # error: Cannot assign to instance variable ``` +## `invalid-await` + + +Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · +[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await) · +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L582) + + +**What it does** + +Checks for `await` being used with types that are not [Awaitable]. + +**Why is this bad?** + +Such expressions will lead to `TypeError` being raised at runtime. + +**Examples** + +```python +import asyncio + +class InvalidAwait: + def __await__(self) -> int: + return 5 + +async def main() -> None: + await InvalidAwait() # error: [invalid-await] + await 42 # error: [invalid-await] + +asyncio.run(main()) +``` + +[Awaitable]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Awaitable + ## `invalid-base` Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L581) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L612) **What it does** @@ -550,7 +584,7 @@ class A(42): ... # error: [invalid-base] Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L632) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L663) **What it does** @@ -575,7 +609,7 @@ with 1: Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L653) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L684) **What it does** @@ -602,7 +636,7 @@ a: str Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L676) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L707) **What it does** @@ -644,7 +678,7 @@ except ZeroDivisionError: Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L712) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L743) **What it does** @@ -675,7 +709,7 @@ class C[U](Generic[T]): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L494) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L495) **What it does** @@ -704,7 +738,7 @@ alice["height"] # KeyError: 'height' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L738) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L769) **What it does** @@ -737,7 +771,7 @@ def f(t: TypeVar("U")): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L787) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L818) **What it does** @@ -769,7 +803,7 @@ class B(metaclass=f): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L814) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L845) **What it does** @@ -817,7 +851,7 @@ def foo(x: int) -> int: ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L857) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L888) **What it does** @@ -841,7 +875,7 @@ def f(a: int = ''): ... Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L422) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L423) **What it does** @@ -873,7 +907,7 @@ TypeError: Protocols can only inherit from other protocols, got Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L877) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L908) Checks for `raise` statements that raise non-exceptions or use invalid @@ -920,7 +954,7 @@ def g(): Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L540) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L541) **What it does** @@ -943,7 +977,7 @@ def func() -> int: Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L920) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L951) **What it does** @@ -997,7 +1031,7 @@ TODO #14889 Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L766) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L797) **What it does** @@ -1022,7 +1056,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L959) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L990) **What it does** @@ -1050,7 +1084,7 @@ TYPE_CHECKING = '' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L983) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1014) **What it does** @@ -1078,7 +1112,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1035) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1066) **What it does** @@ -1110,7 +1144,7 @@ f(10) # Error Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1007) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1038) **What it does** @@ -1142,7 +1176,7 @@ class C: Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1063) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1094) **What it does** @@ -1175,7 +1209,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1092) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1123) **What it does** @@ -1198,7 +1232,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1111) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1142) **What it does** @@ -1225,7 +1259,7 @@ func("string") # error: [no-matching-overload] Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1134) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1165) **What it does** @@ -1247,7 +1281,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1152) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1183) **What it does** @@ -1271,7 +1305,7 @@ for i in 34: # TypeError: 'int' object is not iterable Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1203) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1234) **What it does** @@ -1325,7 +1359,7 @@ def test(): -> "int": Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1539) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1570) **What it does** @@ -1353,7 +1387,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1294) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1325) **What it does** @@ -1380,7 +1414,7 @@ class B(A): ... # Error raised here Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1339) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370) **What it does** @@ -1405,7 +1439,7 @@ f("foo") # Error raised here Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1317) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1348) **What it does** @@ -1431,7 +1465,7 @@ def _(x: int): Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1360) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1391) **What it does** @@ -1475,7 +1509,7 @@ class A: Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1417) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1448) **What it does** @@ -1500,7 +1534,7 @@ f(x=1, y=2) # Error raised here Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1438) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1469) **What it does** @@ -1526,7 +1560,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1460) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1491) **What it does** @@ -1549,7 +1583,7 @@ import foo # ModuleNotFoundError: No module named 'foo' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1479) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1510) **What it does** @@ -1572,7 +1606,7 @@ print(x) # NameError: name 'x' is not defined Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1172) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1203) **What it does** @@ -1607,7 +1641,7 @@ b1 < b2 < b1 # exception raised here Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1498) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1529) **What it does** @@ -1633,7 +1667,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1520) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1551) **What it does** @@ -1656,7 +1690,7 @@ l[1:10:0] # ValueError: slice step cannot be zero Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L266) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L267) **What it does** @@ -1709,7 +1743,7 @@ a = 20 / 0 # type: ignore Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1224) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1255) **What it does** @@ -1735,7 +1769,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L119) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L120) **What it does** @@ -1765,7 +1799,7 @@ A()[0] # TypeError: 'A' object is not subscriptable Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1246) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1277) **What it does** @@ -1795,7 +1829,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1591) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1622) **What it does** @@ -1820,7 +1854,7 @@ cast(int, f()) # Redundant Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1399) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1430) **What it does** @@ -1871,7 +1905,7 @@ a = 20 / 0 # ty: ignore[division-by-zero] Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1612) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1643) **What it does** @@ -1925,7 +1959,7 @@ def g(): Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L599) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L630) **What it does** @@ -1962,7 +1996,7 @@ class D(C): ... # error: [unsupported-base] Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L248) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L249) **What it does** @@ -1984,7 +2018,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) · -[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1272) +[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1303) **What it does** diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md new file mode 100644 index 0000000000..60d3439366 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md @@ -0,0 +1,105 @@ +# Invalid await diagnostics + + + +## Basic + +This is a test showcasing a primitive case where an object is not awaitable. + +```py +async def main() -> None: + await 1 # error: [invalid-await] +``` + +## Custom type with missing `__await__` + +This diagnostic also points to the class definition if available. + +```py +class MissingAwait: + pass + +async def main() -> None: + await MissingAwait() # error: [invalid-await] +``` + +## Custom type with possibly unbound `__await__` + +This diagnostic also points to the method definition if available. + +```py +from datetime import datetime + +class PossiblyUnbound: + if datetime.today().weekday() == 0: + def __await__(self): + yield + +async def main() -> None: + await PossiblyUnbound() # error: [invalid-await] +``` + +## `__await__` definition with extra arguments + +Currently, the signature of `__await__` isn't checked for conformity with the `Awaitable` protocol +directly. Instead, individual anomalies are reported, such as the following. Here, the diagnostic +reports that the object is not implicitly awaitable, while also pointing at the function parameters. + +```py +class InvalidAwaitArgs: + def __await__(self, value: int): + yield value + +async def main() -> None: + await InvalidAwaitArgs() # error: [invalid-await] +``` + +## Non-callable `__await__` + +This diagnostic doesn't point to the attribute definition, but complains about it being possibly not +awaitable. + +```py +class NonCallableAwait: + __await__ = 42 + +async def main() -> None: + await NonCallableAwait() # error: [invalid-await] +``` + +## `__await__` definition with explicit invalid return type + +`__await__` must return a valid iterator. This diagnostic also points to the method definition if +available. + +```py +class InvalidAwaitReturn: + def __await__(self) -> int: + return 5 + +async def main() -> None: + await InvalidAwaitReturn() # error: [invalid-await] +``` + +## Invalid union return type + +When multiple potential definitions of `__await__` exist, all of them must be proper in order for an +instance to be awaitable. In this specific case, no specific function definition is highlighted. + +```py +import typing +from datetime import datetime + +class UnawaitableUnion: + if datetime.today().weekday() == 6: + + def __await__(self) -> typing.Generator[typing.Any, None, None]: + yield + else: + + def __await__(self) -> int: + return 5 + +async def main() -> None: + await UnawaitableUnion() # error: [invalid-await] +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Basic_(f15db7dc447d0795).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Basic_(f15db7dc447d0795).snap new file mode 100644 index 0000000000..c4304a77ee --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Basic_(f15db7dc447d0795).snap @@ -0,0 +1,41 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: invalid_await.md - Invalid await diagnostics - Basic +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | async def main() -> None: +2 | await 1 # error: [invalid-await] +``` + +# Diagnostics + +``` +error[invalid-await]: `Literal[1]` is not awaitable + --> src/mdtest_snippet.py:2:11 + | + 1 | async def main() -> None: + 2 | await 1 # error: [invalid-await] + | ^ + | + ::: stdlib/builtins.pyi:337:7 + | +335 | _LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0] # noqa: Y026 # TODO: Use TypeAlias once mypy bugs are fixed +336 | +337 | class int: + | --- type defined here +338 | """int([x]) -> integer +339 | int(x, base=10) -> integer + | +info: `__await__` is missing +info: rule `invalid-await` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_mis…_(9ce1ee3cd1c9c8d1).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_mis…_(9ce1ee3cd1c9c8d1).snap new file mode 100644 index 0000000000..fcf21676b7 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_mis…_(9ce1ee3cd1c9c8d1).snap @@ -0,0 +1,41 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: invalid_await.md - Invalid await diagnostics - Custom type with missing `__await__` +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | class MissingAwait: +2 | pass +3 | +4 | async def main() -> None: +5 | await MissingAwait() # error: [invalid-await] +``` + +# Diagnostics + +``` +error[invalid-await]: `MissingAwait` is not awaitable + --> src/mdtest_snippet.py:5:11 + | +4 | async def main() -> None: +5 | await MissingAwait() # error: [invalid-await] + | ^^^^^^^^^^^^^^ + | + ::: src/mdtest_snippet.py:1:7 + | +1 | class MissingAwait: + | ------------ type defined here +2 | pass + | +info: `__await__` is missing +info: rule `invalid-await` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(e3444b7a7f960d04).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(e3444b7a7f960d04).snap new file mode 100644 index 0000000000..22233a6acd --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Custom_type_with_pos…_(e3444b7a7f960d04).snap @@ -0,0 +1,47 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: invalid_await.md - Invalid await diagnostics - Custom type with possibly unbound `__await__` +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | from datetime import datetime +2 | +3 | class PossiblyUnbound: +4 | if datetime.today().weekday() == 0: +5 | def __await__(self): +6 | yield +7 | +8 | async def main() -> None: +9 | await PossiblyUnbound() # error: [invalid-await] +``` + +# Diagnostics + +``` +error[invalid-await]: `PossiblyUnbound` is not awaitable + --> src/mdtest_snippet.py:9:11 + | +8 | async def main() -> None: +9 | await PossiblyUnbound() # error: [invalid-await] + | ^^^^^^^^^^^^^^^^^ + | + ::: src/mdtest_snippet.py:5:13 + | +3 | class PossiblyUnbound: +4 | if datetime.today().weekday() == 0: +5 | def __await__(self): + | --------------- method defined here +6 | yield + | +info: `__await__` is possibly unbound +info: rule `invalid-await` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Invalid_union_return…_(fedf62ffaca0f2d7).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Invalid_union_return…_(fedf62ffaca0f2d7).snap new file mode 100644 index 0000000000..302b21afc7 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Invalid_union_return…_(fedf62ffaca0f2d7).snap @@ -0,0 +1,45 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: invalid_await.md - Invalid await diagnostics - Invalid union return type +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | import typing + 2 | from datetime import datetime + 3 | + 4 | class UnawaitableUnion: + 5 | if datetime.today().weekday() == 6: + 6 | + 7 | def __await__(self) -> typing.Generator[typing.Any, None, None]: + 8 | yield + 9 | else: +10 | +11 | def __await__(self) -> int: +12 | return 5 +13 | +14 | async def main() -> None: +15 | await UnawaitableUnion() # error: [invalid-await] +``` + +# Diagnostics + +``` +error[invalid-await]: `UnawaitableUnion` is not awaitable + --> src/mdtest_snippet.py:15:11 + | +14 | async def main() -> None: +15 | await UnawaitableUnion() # error: [invalid-await] + | ^^^^^^^^^^^^^^^^^^ + | +info: `__await__` returns `Generator[Any, None, None] | int`, which is not a valid iterator +info: rule `invalid-await` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Non-callable_`__awai…_(d78580fb6720e4ea).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Non-callable_`__awai…_(d78580fb6720e4ea).snap new file mode 100644 index 0000000000..890ea11b49 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_Non-callable_`__awai…_(d78580fb6720e4ea).snap @@ -0,0 +1,35 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: invalid_await.md - Invalid await diagnostics - Non-callable `__await__` +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | class NonCallableAwait: +2 | __await__ = 42 +3 | +4 | async def main() -> None: +5 | await NonCallableAwait() # error: [invalid-await] +``` + +# Diagnostics + +``` +error[invalid-await]: `NonCallableAwait` is not awaitable + --> src/mdtest_snippet.py:5:11 + | +4 | async def main() -> None: +5 | await NonCallableAwait() # error: [invalid-await] + | ^^^^^^^^^^^^^^^^^^ + | +info: `__await__` is possibly not callable +info: rule `invalid-await` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_`__await__`_definiti…_(15b05c126b6ae968).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_`__await__`_definiti…_(15b05c126b6ae968).snap new file mode 100644 index 0000000000..c5fe4db415 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_`__await__`_definiti…_(15b05c126b6ae968).snap @@ -0,0 +1,43 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: invalid_await.md - Invalid await diagnostics - `__await__` definition with extra arguments +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | class InvalidAwaitArgs: +2 | def __await__(self, value: int): +3 | yield value +4 | +5 | async def main() -> None: +6 | await InvalidAwaitArgs() # error: [invalid-await] +``` + +# Diagnostics + +``` +error[invalid-await]: `InvalidAwaitArgs` is not awaitable + --> src/mdtest_snippet.py:6:11 + | +5 | async def main() -> None: +6 | await InvalidAwaitArgs() # error: [invalid-await] + | ^^^^^^^^^^^^^^^^^^ + | + ::: src/mdtest_snippet.py:2:18 + | +1 | class InvalidAwaitArgs: +2 | def __await__(self, value: int): + | ------------------ parameters here +3 | yield value + | +info: `__await__` requires arguments and cannot be called implicitly +info: rule `invalid-await` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_`__await__`_definiti…_(ccb69f512135dd61).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_`__await__`_definiti…_(ccb69f512135dd61).snap new file mode 100644 index 0000000000..b7fa723787 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid_await.md_-_Invalid_await_diagno…_-_`__await__`_definiti…_(ccb69f512135dd61).snap @@ -0,0 +1,43 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: invalid_await.md - Invalid await diagnostics - `__await__` definition with explicit invalid return type +mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_await.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | class InvalidAwaitReturn: +2 | def __await__(self) -> int: +3 | return 5 +4 | +5 | async def main() -> None: +6 | await InvalidAwaitReturn() # error: [invalid-await] +``` + +# Diagnostics + +``` +error[invalid-await]: `InvalidAwaitReturn` is not awaitable + --> src/mdtest_snippet.py:6:11 + | +5 | async def main() -> None: +6 | await InvalidAwaitReturn() # error: [invalid-await] + | ^^^^^^^^^^^^^^^^^^^^ + | + ::: src/mdtest_snippet.py:2:9 + | +1 | class InvalidAwaitReturn: +2 | def __await__(self) -> int: + | ---------------------- method defined here +3 | return 5 + | +info: `__await__` returns `int`, which is not a valid iterator +info: rule `invalid-await` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 2ae65d7f90..89b66a6b34 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -41,7 +41,7 @@ use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding}; use crate::types::class::{CodeGeneratorKind, Field}; pub(crate) use crate::types::class_base::ClassBase; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; -use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; +use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; use crate::types::enums::{enum_metadata, is_single_member_enum}; use crate::types::function::{ DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, @@ -4778,20 +4778,24 @@ impl<'db> Type<'db> { mode: EvaluationMode, ) -> Result>, IterationError<'db>> { if mode.is_async() { - let try_call_dunder_anext_on_iterator = |iterator: Type<'db>| { + let try_call_dunder_anext_on_iterator = |iterator: Type<'db>| -> Result< + Result, AwaitError<'db>>, + CallDunderError<'db>, + > { iterator .try_call_dunder(db, "__anext__", CallArguments::none()) - .map(|dunder_anext_outcome| { - dunder_anext_outcome.return_type(db).resolve_await(db) - }) + .map(|dunder_anext_outcome| dunder_anext_outcome.return_type(db).try_await(db)) }; return match self.try_call_dunder(db, "__aiter__", CallArguments::none()) { Ok(dunder_aiter_bindings) => { let iterator = dunder_aiter_bindings.return_type(db); match try_call_dunder_anext_on_iterator(iterator) { - Ok(result) => Ok(Cow::Owned(TupleSpec::homogeneous(result))), - Err(dunder_anext_error) => { + Ok(Ok(result)) => Ok(Cow::Owned(TupleSpec::homogeneous(result))), + Ok(Err(AwaitError::InvalidReturnType(..))) => { + Err(IterationError::UnboundAiterError) + } // TODO: __anext__ is bound, but is not properly awaitable + Err(dunder_anext_error) | Ok(Err(AwaitError::Call(dunder_anext_error))) => { Err(IterationError::IterReturnsInvalidIterator { iterator, dunder_error: dunder_anext_error, @@ -4996,7 +5000,7 @@ impl<'db> Type<'db> { (Ok(enter), Ok(_)) => { let ty = enter.return_type(db); Ok(if mode.is_async() { - ty.resolve_await(db) + ty.try_await(db).unwrap_or(Type::unknown()) } else { ty }) @@ -5005,7 +5009,7 @@ impl<'db> Type<'db> { let ty = enter.return_type(db); Err(ContextManagerError::Exit { enter_return_type: if mode.is_async() { - ty.resolve_await(db) + ty.try_await(db).unwrap_or(Type::unknown()) } else { ty }, @@ -5024,15 +5028,17 @@ impl<'db> Type<'db> { } /// Resolve the type of an `await …` expression where `self` is the type of the awaitable. - fn resolve_await(self, db: &'db dyn Db) -> Type<'db> { - // TODO: Add proper error handling and rename this method to `try_await`. - self.try_call_dunder(db, "__await__", CallArguments::none()) - .map_or(Type::unknown(), |result| { - result - .return_type(db) - .generator_return_type(db) - .unwrap_or_else(Type::unknown) - }) + fn try_await(self, db: &'db dyn Db) -> Result, AwaitError<'db>> { + let await_result = self.try_call_dunder(db, "__await__", CallArguments::none()); + match await_result { + Ok(bindings) => { + let return_type = bindings.return_type(db); + Ok(return_type.generator_return_type(db).ok_or_else(|| { + AwaitError::InvalidReturnType(return_type, Box::new(bindings)) + })?) + } + Err(call_error) => Err(AwaitError::Call(call_error)), + } } /// Get the return type of a `yield from …` expression where `self` is the type of the generator. @@ -5068,6 +5074,8 @@ impl<'db> Type<'db> { None } } + Type::Union(union) => union.try_map(db, |ty| ty.generator_return_type(db)), + ty @ (Type::Dynamic(_) | Type::Never) => Some(ty), _ => None, } } @@ -7224,6 +7232,97 @@ impl<'db> TypeVarBoundOrConstraints<'db> { } } +/// Error returned if a type is not awaitable. +#[derive(Debug)] +enum AwaitError<'db> { + /// `__await__` is either missing, potentially unbound or cannot be called with provided + /// arguments. + Call(CallDunderError<'db>), + /// `__await__` resolved successfully, but its return type is known not to be a generator. + InvalidReturnType(Type<'db>, Box>), +} + +impl<'db> AwaitError<'db> { + fn report_diagnostic( + &self, + context: &InferContext<'db, '_>, + context_expression_type: Type<'db>, + context_expression_node: ast::AnyNodeRef, + ) { + let Some(builder) = context.report_lint(&INVALID_AWAIT, context_expression_node) else { + return; + }; + + let db = context.db(); + + let mut diag = builder.into_diagnostic( + format_args!("`{type}` is not awaitable", type = context_expression_type.display(db)), + ); + match self { + Self::Call(CallDunderError::CallError(CallErrorKind::BindingError, bindings)) => { + diag.info("`__await__` requires arguments and cannot be called implicitly"); + if let Some(definition_spans) = bindings.callable_type().function_spans(db) { + diag.annotate( + Annotation::secondary(definition_spans.parameters) + .message("parameters here"), + ); + } + } + Self::Call(CallDunderError::CallError( + kind @ (CallErrorKind::NotCallable | CallErrorKind::PossiblyNotCallable), + bindings, + )) => { + let possibly = if matches!(kind, CallErrorKind::PossiblyNotCallable) { + " possibly" + } else { + "" + }; + diag.info(format_args!("`__await__` is{possibly} not callable")); + if let Some(definition) = bindings.callable_type().definition(db) { + if let Some(definition_range) = definition.focus_range(db) { + diag.annotate( + Annotation::secondary(definition_range.into()) + .message("attribute defined here"), + ); + } + } + } + Self::Call(CallDunderError::PossiblyUnbound(bindings)) => { + diag.info("`__await__` is possibly unbound"); + if let Some(definition_spans) = bindings.callable_type().function_spans(db) { + diag.annotate( + Annotation::secondary(definition_spans.signature) + .message("method defined here"), + ); + } + } + Self::Call(CallDunderError::MethodNotAvailable) => { + diag.info("`__await__` is missing"); + if let Some(type_definition) = context_expression_type.definition(db) { + if let Some(definition_range) = type_definition.focus_range(db) { + diag.annotate( + Annotation::secondary(definition_range.into()) + .message("type defined here"), + ); + } + } + } + Self::InvalidReturnType(return_type, bindings) => { + diag.info(format_args!( + "`__await__` returns `{return_type}`, which is not a valid iterator", + return_type = return_type.display(db) + )); + if let Some(definition_spans) = bindings.callable_type().function_spans(db) { + diag.annotate( + Annotation::secondary(definition_spans.signature) + .message("method defined here"), + ); + } + } + } + } +} + /// Error returned if a type is not (or may not be) a context manager. #[derive(Debug)] enum ContextManagerError<'db> { @@ -7447,11 +7546,11 @@ impl<'db> IterationError<'db> { match self { Self::IterReturnsInvalidIterator { dunder_error, mode, .. - } => dunder_error.return_type(db).map(|ty| { + } => dunder_error.return_type(db).and_then(|ty| { if mode.is_async() { - ty.resolve_await(db) + ty.try_await(db).ok() } else { - ty + Some(ty) } }), @@ -7466,7 +7565,7 @@ impl<'db> IterationError<'db> { "__anext__", CallArguments::none(), )) - .map(|ty| ty.resolve_await(db)) + .and_then(|ty| ty.try_await(db).ok()) } else { return_type(dunder_iter_bindings.return_type(db).try_call_dunder( db, diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 6339383005..113d0fa508 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -45,6 +45,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) { registry.register_lint(&INVALID_ARGUMENT_TYPE); registry.register_lint(&INVALID_RETURN_TYPE); registry.register_lint(&INVALID_ASSIGNMENT); + registry.register_lint(&INVALID_AWAIT); registry.register_lint(&INVALID_BASE); registry.register_lint(&INVALID_CONTEXT_MANAGER); registry.register_lint(&INVALID_DECLARATION); @@ -578,6 +579,36 @@ declare_lint! { } } +declare_lint! { + /// ## What it does + /// Checks for `await` being used with types that are not [Awaitable]. + /// + /// ## Why is this bad? + /// Such expressions will lead to `TypeError` being raised at runtime. + /// + /// ## Examples + /// ```python + /// import asyncio + /// + /// class InvalidAwait: + /// def __await__(self) -> int: + /// return 5 + /// + /// async def main() -> None: + /// await InvalidAwait() # error: [invalid-await] + /// await 42 # error: [invalid-await] + /// + /// asyncio.run(main()) + /// ``` + /// + /// [Awaitable]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Awaitable + pub(crate) static INVALID_AWAIT = { + summary: "detects awaiting on types that don't support it", + status: LintStatus::preview("1.0.0"), + default_level: Level::Error, + } +} + declare_lint! { /// ## What it does /// Checks for class definitions that have bases which are not instances of `type`. diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 8a38ea0a84..632425f16c 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -94,7 +94,6 @@ pub(crate) struct FunctionSpans { pub(crate) name: Span, /// The span of the parameter list, including the opening and /// closing parentheses. - #[expect(dead_code)] pub(crate) parameters: Span, /// The span of the annotated return type, if present. pub(crate) return_type: Option, diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 8bba369c3b..3f8ade1195 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -6380,7 +6380,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { node_index: _, value, } = await_expression; - self.infer_expression(value).resolve_await(self.db()) + let expr_type = self.infer_expression(value); + expr_type.try_await(self.db()).unwrap_or_else(|err| { + err.report_diagnostic(&self.context, expr_type, value.as_ref().into()); + Type::unknown() + }) } // Perform narrowing with applicable constraints between the current scope and the enclosing scope. diff --git a/ty.schema.json b/ty.schema.json index bcec1c7685..d94057ef9e 100644 --- a/ty.schema.json +++ b/ty.schema.json @@ -461,6 +461,16 @@ } ] }, + "invalid-await": { + "title": "detects awaiting on types that don't support it", + "description": "## What it does\nChecks for `await` being used with types that are not [Awaitable].\n\n## Why is this bad?\nSuch expressions will lead to `TypeError` being raised at runtime.\n\n## Examples\n```python\nimport asyncio\n\nclass InvalidAwait:\n def __await__(self) -> int:\n return 5\n\nasync def main() -> None:\n await InvalidAwait() # error: [invalid-await]\n await 42 # error: [invalid-await]\n\nasyncio.run(main())\n```\n\n[Awaitable]: https://docs.python.org/3/library/collections.abc.html#collections.abc.Awaitable", + "default": "error", + "oneOf": [ + { + "$ref": "#/definitions/Level" + } + ] + }, "invalid-base": { "title": "detects class bases that will cause the class definition to raise an exception at runtime", "description": "## What it does\nChecks for class definitions that have bases which are not instances of `type`.\n\n## Why is this bad?\nClass definitions with bases like this will lead to `TypeError` being raised at runtime.\n\n## Examples\n```python\nclass A(42): ... # error: [invalid-base]\n```",