Compare commits

...

7 Commits

Author SHA1 Message Date
Alex Waygood
dec1d2e8fc ZeroDivisionError for complex too 2025-07-14 17:54:58 +01:00
Alex Waygood
3aa91a853e add tests for bools and make helper method private 2025-07-14 17:48:16 +01:00
Alex Waygood
59570beb57 combine the two rules 2025-07-14 17:48:11 +01:00
Alex Waygood
78e51e8601 post-rebase fixups 2025-07-14 17:46:32 +01:00
Brandt Bucher
0006df9292 Fix unrelated negation edge case 2025-07-14 17:46:32 +01:00
Brandt Bucher
14aa8bb871 ...of *course* the issue's with my Python code... 2025-07-14 17:46:32 +01:00
Brandt Bucher
2882840abc Check shifts of literals ints 2025-07-14 17:46:32 +01:00
17 changed files with 360 additions and 253 deletions

View File

@@ -5,4 +5,4 @@
[rules]
possibly-unresolved-reference = "warn"
unused-ignore-comment = "warn"
division-by-zero = "warn"
literal-math-error = "warn"

View File

@@ -23,7 +23,7 @@ Valid severities are:
```toml
[tool.ty.rules]
possibly-unresolved-reference = "warn"
division-by-zero = "ignore"
literal-math-error = "ignore"
```
---

117
crates/ty/docs/rules.md generated
View File

@@ -177,7 +177,7 @@ class B(A): ...
<small>
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#L263)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L266)
</small>
**What it does**
@@ -202,7 +202,7 @@ class B(A, A): ...
<small>
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#L284)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L287)
</small>
**What it does**
@@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
<small>
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#L426)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L429)
</small>
**What it does**
@@ -334,7 +334,7 @@ class C(A, B): ...
<small>
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#L450)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L453)
</small>
**What it does**
@@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
<small>
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#L316)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L319)
</small>
**What it does**
@@ -445,7 +445,7 @@ an atypical memory layout.
<small>
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#L470)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L473)
</small>
**What it does**
@@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
<small>
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#L510)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L513)
</small>
**What it does**
@@ -496,7 +496,7 @@ a: int = ''
<small>
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#L1514)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1517)
</small>
**What it does**
@@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
<small>
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#L532)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L535)
</small>
**What it does**
@@ -550,7 +550,7 @@ class A(42): ... # error: [invalid-base]
<small>
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#L583)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L586)
</small>
**What it does**
@@ -575,7 +575,7 @@ with 1:
<small>
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#L604)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L607)
</small>
**What it does**
@@ -602,7 +602,7 @@ a: str
<small>
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#L627)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L630)
</small>
**What it does**
@@ -644,7 +644,7 @@ except ZeroDivisionError:
<small>
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#L663)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L666)
</small>
**What it does**
@@ -675,7 +675,7 @@ class C[U](Generic[T]): ...
<small>
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#L689)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L692)
</small>
**What it does**
@@ -708,7 +708,7 @@ def f(t: TypeVar("U")): ...
<small>
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#L738)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L741)
</small>
**What it does**
@@ -740,7 +740,7 @@ class B(metaclass=f): ...
<small>
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#L765)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L768)
</small>
**What it does**
@@ -788,7 +788,7 @@ def foo(x: int) -> int: ...
<small>
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#L808)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L811)
</small>
**What it does**
@@ -812,7 +812,7 @@ def f(a: int = ''): ...
<small>
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#L398)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L401)
</small>
**What it does**
@@ -844,7 +844,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
<small>
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#L828)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L831)
</small>
Checks for `raise` statements that raise non-exceptions or use invalid
@@ -891,7 +891,7 @@ def g():
<small>
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#L491)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L494)
</small>
**What it does**
@@ -914,7 +914,7 @@ def func() -> int:
<small>
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#L871)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L874)
</small>
**What it does**
@@ -968,7 +968,7 @@ TODO #14889
<small>
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#L717)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L720)
</small>
**What it does**
@@ -993,7 +993,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
<small>
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#L910)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L913)
</small>
**What it does**
@@ -1021,7 +1021,7 @@ TYPE_CHECKING = ''
<small>
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#L934)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L937)
</small>
**What it does**
@@ -1049,7 +1049,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
<small>
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#L986)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L989)
</small>
**What it does**
@@ -1081,7 +1081,7 @@ f(10) # Error
<small>
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#L958)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L961)
</small>
**What it does**
@@ -1113,7 +1113,7 @@ class C:
<small>
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#L1014)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1017)
</small>
**What it does**
@@ -1146,7 +1146,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
<small>
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#L1043)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1046)
</small>
**What it does**
@@ -1169,7 +1169,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
<small>
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#L1062)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1065)
</small>
**What it does**
@@ -1196,7 +1196,7 @@ func("string") # error: [no-matching-overload]
<small>
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#L1085)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1088)
</small>
**What it does**
@@ -1218,7 +1218,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
<small>
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#L1103)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106)
</small>
**What it does**
@@ -1242,7 +1242,7 @@ for i in 34: # TypeError: 'int' object is not iterable
<small>
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#L1154)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1157)
</small>
**What it does**
@@ -1296,7 +1296,7 @@ def test(): -> "int":
<small>
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#L1490)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1493)
</small>
**What it does**
@@ -1324,7 +1324,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
<small>
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#L1245)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1248)
</small>
**What it does**
@@ -1351,7 +1351,7 @@ class B(A): ... # Error raised here
<small>
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#L1290)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293)
</small>
**What it does**
@@ -1376,7 +1376,7 @@ f("foo") # Error raised here
<small>
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#L1268)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271)
</small>
**What it does**
@@ -1402,7 +1402,7 @@ def _(x: int):
<small>
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#L1311)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1314)
</small>
**What it does**
@@ -1446,7 +1446,7 @@ class A:
<small>
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#L1368)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1371)
</small>
**What it does**
@@ -1471,7 +1471,7 @@ f(x=1, y=2) # Error raised here
<small>
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#L1389)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1392)
</small>
**What it does**
@@ -1497,7 +1497,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
<small>
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#L1411)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1414)
</small>
**What it does**
@@ -1520,7 +1520,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
<small>
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#L1430)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1433)
</small>
**What it does**
@@ -1543,7 +1543,7 @@ print(x) # NameError: name 'x' is not defined
<small>
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#L1123)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1126)
</small>
**What it does**
@@ -1578,7 +1578,7 @@ b1 < b2 < b1 # exception raised here
<small>
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#L1449)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1452)
</small>
**What it does**
@@ -1604,7 +1604,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
<small>
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#L1471)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1474)
</small>
**What it does**
@@ -1655,7 +1655,7 @@ a = 20 / 0 # type: ignore
<small>
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#L1175)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1178)
</small>
**What it does**
@@ -1711,7 +1711,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
<small>
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#L1197)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1200)
</small>
**What it does**
@@ -1741,7 +1741,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
<small>
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#L1542)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1545)
</small>
**What it does**
@@ -1766,7 +1766,7 @@ cast(int, f()) # Redundant
<small>
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#L1350)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1353)
</small>
**What it does**
@@ -1809,7 +1809,7 @@ a = 20 / 0 # ty: ignore[division-by-zer]
Use instead:
```py
a = 20 / 0 # ty: ignore[division-by-zero]
a = 20 / 0 # ty: ignore[literal-math-error]
```
## `unsupported-base`
@@ -1817,7 +1817,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
<small>
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#L550)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L553)
</small>
**What it does**
@@ -1849,26 +1849,29 @@ class D(C): ... # error: [unsupported-base]
[method resolution order]: https://docs.python.org/3/glossary.html#term-method-resolution-order
## `division-by-zero`
## `literal-math-error`
<small>
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) ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20literal-math-error) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L245)
</small>
**What it does**
It detects division by zero.
Detects runtime errors that would result from invalid math operations
between two objects with literal `int` types. Examples include division
by zero and negative bitshifts.
**Why is this bad?**
Dividing by zero raises a `ZeroDivisionError` at runtime.
These math operations will lead to exceptions being raised at runtime.
**Examples**
```python
5 / 0
5 / 0 # `ZeroDivisionError`
1 << -1 # `ValueError: negative shift count`
```
## `possibly-unresolved-reference`
@@ -1876,7 +1879,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
<small>
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#L1223)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1226)
</small>
**What it does**
@@ -1917,7 +1920,7 @@ included by mistake, and should be removed to avoid confusion.
**Examples**
```py
a = 20 / 2 # ty: ignore[division-by-zero]
a = 20 / 2 # ty: ignore[literal-math-error]
```
Use instead:

View File

@@ -327,7 +327,7 @@ fn user_configuration() -> anyhow::Result<()> {
"project/ty.toml",
r#"
[rules]
division-by-zero = "warn"
literal-math-error = "warn"
"#,
),
(
@@ -356,7 +356,7 @@ fn user_configuration() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> main.py:2:5
|
2 | y = 4 / 0
@@ -364,7 +364,7 @@ fn user_configuration() -> anyhow::Result<()> {
3 |
4 | for a in range(0, int(y)):
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
error[unresolved-reference]: Name `prin` used when not defined
--> main.py:7:1
@@ -384,13 +384,13 @@ fn user_configuration() -> anyhow::Result<()> {
);
// The user-level configuration sets the severity for `unresolved-reference` to warn.
// Changing the level for `division-by-zero` has no effect, because the project-level configuration
// Changing the level for `literal-math-error` has no effect, because the project-level configuration
// has higher precedence.
case.write_file(
config_directory.join("ty/ty.toml"),
r#"
[rules]
division-by-zero = "error"
literal-math-error = "error"
unresolved-reference = "warn"
"#,
)?;
@@ -401,7 +401,7 @@ fn user_configuration() -> anyhow::Result<()> {
success: true
exit_code: 0
----- stdout -----
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> main.py:2:5
|
2 | y = 4 / 0
@@ -409,7 +409,7 @@ fn user_configuration() -> anyhow::Result<()> {
3 |
4 | for a in range(0, int(y)):
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
warning[unresolved-reference]: Name `prin` used when not defined
--> main.py:7:1
@@ -437,7 +437,7 @@ fn check_specific_paths() -> anyhow::Result<()> {
(
"project/main.py",
r#"
y = 4 / 0 # error: division-by-zero
y = 4 / 0 # error: literal-math-error
"#,
),
(

View File

@@ -42,7 +42,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "warn" # promote to warn
literal-math-error = "warn" # promote to warn
unresolved-reference = "ignore"
"#,
)?;
@@ -51,7 +51,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
success: true
exit_code: 0
----- stdout -----
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> test.py:2:5
|
2 | y = 4 / 0
@@ -59,7 +59,7 @@ fn configuration_rule_severity() -> anyhow::Result<()> {
3 |
4 | for a in range(0, int(y)):
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
Found 1 diagnostic
@@ -126,7 +126,7 @@ fn cli_rule_severity() -> anyhow::Result<()> {
.arg("--ignore")
.arg("unresolved-reference")
.arg("--warn")
.arg("division-by-zero")
.arg("literal-math-error")
.arg("--warn")
.arg("unresolved-import"),
@r"
@@ -144,7 +144,7 @@ fn cli_rule_severity() -> anyhow::Result<()> {
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
info: rule `unresolved-import` was selected on the command line
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> test.py:4:5
|
2 | import does_not_exit
@@ -154,7 +154,7 @@ fn cli_rule_severity() -> anyhow::Result<()> {
5 |
6 | for a in range(0, int(y)):
|
info: rule `division-by-zero` was selected on the command line
info: rule `literal-math-error` was selected on the command line
Found 2 diagnostics
@@ -209,14 +209,14 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
.arg("--warn")
.arg("unresolved-reference")
.arg("--warn")
.arg("division-by-zero")
.arg("literal-math-error")
.arg("--ignore")
.arg("unresolved-reference"),
@r"
success: true
exit_code: 0
----- stdout -----
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> test.py:2:5
|
2 | y = 4 / 0
@@ -224,7 +224,7 @@ fn cli_rule_severity_precedence() -> anyhow::Result<()> {
3 |
4 | for a in range(0, int(y)):
|
info: rule `division-by-zero` was selected on the command line
info: rule `literal-math-error` was selected on the command line
Found 1 diagnostic
@@ -299,21 +299,21 @@ fn overrides_basic() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "error"
literal-math-error = "error"
unresolved-reference = "error"
[[tool.ty.overrides]]
include = ["tests/**"]
[tool.ty.overrides.rules]
division-by-zero = "warn"
literal-math-error = "warn"
unresolved-reference = "ignore"
"#,
),
(
"main.py",
r#"
y = 4 / 0 # division-by-zero: error (global)
y = 4 / 0 # literal-math-error: error (global)
x = 1
prin(x) # unresolved-reference: error (global)
"#,
@@ -321,7 +321,7 @@ fn overrides_basic() -> anyhow::Result<()> {
(
"tests/test_main.py",
r#"
y = 4 / 0 # division-by-zero: warn (override)
y = 4 / 0 # literal-math-error: warn (override)
x = 1
prin(x) # unresolved-reference: ignore (override)
"#,
@@ -332,35 +332,35 @@ fn overrides_basic() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
error[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> main.py:2:5
|
2 | y = 4 / 0 # division-by-zero: error (global)
2 | y = 4 / 0 # literal-math-error: error (global)
| ^^^^^
3 | x = 1
4 | prin(x) # unresolved-reference: error (global)
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
error[unresolved-reference]: Name `prin` used when not defined
--> main.py:4:1
|
2 | y = 4 / 0 # division-by-zero: error (global)
2 | y = 4 / 0 # literal-math-error: error (global)
3 | x = 1
4 | prin(x) # unresolved-reference: error (global)
| ^^^^
|
info: rule `unresolved-reference` was selected in the configuration file
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> tests/test_main.py:2:5
|
2 | y = 4 / 0 # division-by-zero: warn (override)
2 | y = 4 / 0 # literal-math-error: warn (override)
| ^^^^^
3 | x = 1
4 | prin(x) # unresolved-reference: ignore (override)
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
Found 3 diagnostics
@@ -379,31 +379,31 @@ fn overrides_precedence() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "error"
literal-math-error = "error"
# First override: all test files
[[tool.ty.overrides]]
include = ["tests/**"]
[tool.ty.overrides.rules]
division-by-zero = "warn"
literal-math-error = "warn"
# Second override: specific test file (takes precedence)
[[tool.ty.overrides]]
include = ["tests/important.py"]
[tool.ty.overrides.rules]
division-by-zero = "ignore"
literal-math-error = "ignore"
"#,
),
(
"tests/test_main.py",
r#"
y = 4 / 0 # division-by-zero: warn (first override)
y = 4 / 0 # literal-math-error: warn (first override)
"#,
),
(
"tests/important.py",
r#"
y = 4 / 0 # division-by-zero: ignore (second override)
y = 4 / 0 # literal-math-error: ignore (second override)
"#,
),
])?;
@@ -412,13 +412,13 @@ fn overrides_precedence() -> anyhow::Result<()> {
success: true
exit_code: 0
----- stdout -----
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> tests/test_main.py:2:5
|
2 | y = 4 / 0 # division-by-zero: warn (first override)
2 | y = 4 / 0 # literal-math-error: warn (first override)
| ^^^^^
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
Found 1 diagnostic
@@ -437,25 +437,25 @@ fn overrides_exclude() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "error"
literal-math-error = "error"
[[tool.ty.overrides]]
include = ["tests/**"]
exclude = ["tests/important.py"]
[tool.ty.overrides.rules]
division-by-zero = "warn"
literal-math-error = "warn"
"#,
),
(
"tests/test_main.py",
r#"
y = 4 / 0 # division-by-zero: warn (override applies)
y = 4 / 0 # literal-math-error: warn (override applies)
"#,
),
(
"tests/important.py",
r#"
y = 4 / 0 # division-by-zero: error (override excluded)
y = 4 / 0 # literal-math-error: error (override excluded)
"#,
),
])?;
@@ -464,21 +464,21 @@ fn overrides_exclude() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
error[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> tests/important.py:2:5
|
2 | y = 4 / 0 # division-by-zero: error (override excluded)
2 | y = 4 / 0 # literal-math-error: error (override excluded)
| ^^^^^
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> tests/test_main.py:2:5
|
2 | y = 4 / 0 # division-by-zero: warn (override applies)
2 | y = 4 / 0 # literal-math-error: warn (override applies)
| ^^^^^
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
Found 2 diagnostics
@@ -497,28 +497,28 @@ fn overrides_inherit_global() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "warn"
literal-math-error = "warn"
unresolved-reference = "error"
[[tool.ty.overrides]]
include = ["tests/**"]
[tool.ty.overrides.rules]
# Override only division-by-zero, unresolved-reference should inherit from global
division-by-zero = "ignore"
# Override only literal-math-error, unresolved-reference should inherit from global
literal-math-error = "ignore"
"#,
),
(
"main.py",
r#"
y = 4 / 0 # division-by-zero: warn (global)
y = 4 / 0 # literal-math-error: warn (global)
prin(y) # unresolved-reference: error (global)
"#,
),
(
"tests/test_main.py",
r#"
y = 4 / 0 # division-by-zero: ignore (overridden)
y = 4 / 0 # literal-math-error: ignore (overridden)
prin(y) # unresolved-reference: error (inherited from global)
"#,
),
@@ -528,19 +528,19 @@ fn overrides_inherit_global() -> anyhow::Result<()> {
success: false
exit_code: 1
----- stdout -----
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> main.py:2:5
|
2 | y = 4 / 0 # division-by-zero: warn (global)
2 | y = 4 / 0 # literal-math-error: warn (global)
| ^^^^^
3 | prin(y) # unresolved-reference: error (global)
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
error[unresolved-reference]: Name `prin` used when not defined
--> main.py:3:1
|
2 | y = 4 / 0 # division-by-zero: warn (global)
2 | y = 4 / 0 # literal-math-error: warn (global)
3 | prin(y) # unresolved-reference: error (global)
| ^^^^
|
@@ -549,7 +549,7 @@ fn overrides_inherit_global() -> anyhow::Result<()> {
error[unresolved-reference]: Name `prin` used when not defined
--> tests/test_main.py:3:1
|
2 | y = 4 / 0 # division-by-zero: ignore (overridden)
2 | y = 4 / 0 # literal-math-error: ignore (overridden)
3 | prin(y) # unresolved-reference: error (inherited from global)
| ^^^^
|
@@ -572,12 +572,12 @@ fn overrides_invalid_include_glob() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "error"
literal-math-error = "error"
[[tool.ty.overrides]]
include = ["tests/[invalid"] # Invalid glob: unclosed bracket
[tool.ty.overrides.rules]
division-by-zero = "warn"
literal-math-error = "warn"
"#,
),
(
@@ -603,7 +603,7 @@ fn overrides_invalid_include_glob() -> anyhow::Result<()> {
6 | include = ["tests/[invalid"] # Invalid glob: unclosed bracket
| ^^^^^^^^^^^^^^^^ unclosed character class; missing ']'
7 | [tool.ty.overrides.rules]
8 | division-by-zero = "warn"
8 | literal-math-error = "warn"
|
"#);
@@ -618,13 +618,13 @@ fn overrides_invalid_exclude_glob() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "error"
literal-math-error = "error"
[[tool.ty.overrides]]
include = ["tests/**"]
exclude = ["***/invalid"] # Invalid glob: triple asterisk
[tool.ty.overrides.rules]
division-by-zero = "warn"
literal-math-error = "warn"
"#,
),
(
@@ -651,7 +651,7 @@ fn overrides_invalid_exclude_glob() -> anyhow::Result<()> {
7 | exclude = ["***/invalid"] # Invalid glob: triple asterisk
| ^^^^^^^^^^^^^ Too many stars at position 1
8 | [tool.ty.overrides.rules]
9 | division-by-zero = "warn"
9 | literal-math-error = "warn"
|
"#);
@@ -666,12 +666,12 @@ fn overrides_missing_include_exclude() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "error"
literal-math-error = "error"
[[tool.ty.overrides]]
# Missing both include and exclude - should warn
[tool.ty.overrides.rules]
division-by-zero = "warn"
literal-math-error = "warn"
"#,
),
(
@@ -689,7 +689,7 @@ fn overrides_missing_include_exclude() -> anyhow::Result<()> {
warning[unnecessary-overrides-section]: Unnecessary `overrides` section
--> pyproject.toml:5:1
|
3 | division-by-zero = "error"
3 | literal-math-error = "error"
4 |
5 | [[tool.ty.overrides]]
| ^^^^^^^^^^^^^^^^^^^^^ This overrides section applies to all files
@@ -700,13 +700,13 @@ fn overrides_missing_include_exclude() -> anyhow::Result<()> {
info: Restrict the files by adding a pattern to `include` or `exclude`...
info: or remove the `[[overrides]]` section and merge the configuration into the root `[rules]` table if the configuration should apply to all files
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> test.py:2:5
|
2 | y = 4 / 0
| ^^^^^
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
Found 2 diagnostics
@@ -725,12 +725,12 @@ fn overrides_empty_include() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "error"
literal-math-error = "error"
[[tool.ty.overrides]]
include = [] # Empty include - won't match any files
[tool.ty.overrides.rules]
division-by-zero = "warn"
literal-math-error = "warn"
"#,
),
(
@@ -752,17 +752,17 @@ fn overrides_empty_include() -> anyhow::Result<()> {
6 | include = [] # Empty include - won't match any files
| ^^ This `include` list is empty
7 | [tool.ty.overrides.rules]
8 | division-by-zero = "warn"
8 | literal-math-error = "warn"
|
info: Remove the `include` option to match all files or add a pattern to match specific files
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
error[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> test.py:2:5
|
2 | y = 4 / 0
| ^^^^^
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
Found 2 diagnostics
@@ -781,7 +781,7 @@ fn overrides_no_actual_overrides() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "error"
literal-math-error = "error"
[[tool.ty.overrides]]
include = ["*.py"] # Has patterns but no rule overrides
@@ -803,7 +803,7 @@ fn overrides_no_actual_overrides() -> anyhow::Result<()> {
warning[useless-overrides-section]: Useless `overrides` section
--> pyproject.toml:5:1
|
3 | division-by-zero = "error"
3 | literal-math-error = "error"
4 |
5 | [[tool.ty.overrides]]
| ^^^^^^^^^^^^^^^^^^^^^ This overrides section configures no rules
@@ -814,13 +814,13 @@ fn overrides_no_actual_overrides() -> anyhow::Result<()> {
info: Add a `[overrides.rules]` table...
info: or remove the `[[overrides]]` section if there's nothing to override
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
error[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> test.py:2:5
|
2 | y = 4 / 0
| ^^^^^
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
Found 2 diagnostics
@@ -839,13 +839,13 @@ fn overrides_unknown_rules() -> anyhow::Result<()> {
"pyproject.toml",
r#"
[tool.ty.rules]
division-by-zero = "error"
literal-math-error = "error"
[[tool.ty.overrides]]
include = ["tests/**"]
[tool.ty.overrides.rules]
division-by-zero = "warn"
literal-math-error = "warn"
division-by-zer = "error" # incorrect rule name
"#,
),
@@ -871,26 +871,26 @@ fn overrides_unknown_rules() -> anyhow::Result<()> {
--> pyproject.toml:10:1
|
8 | [tool.ty.overrides.rules]
9 | division-by-zero = "warn"
9 | literal-math-error = "warn"
10 | division-by-zer = "error" # incorrect rule name
| ^^^^^^^^^^^^^^^
|
error[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
error[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> main.py:2:5
|
2 | y = 4 / 0
| ^^^^^
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
warning[division-by-zero]: Cannot divide object of type `Literal[4]` by zero
warning[literal-math-error]: Cannot divide object of type `Literal[4]` by zero
--> tests/test_main.py:2:5
|
2 | y = 4 / 0
| ^^^^^
|
info: rule `division-by-zero` was selected in the configuration file
info: rule `literal-math-error` was selected in the configuration file
Found 3 diagnostics

View File

@@ -1687,7 +1687,7 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
config_directory.join("ty/ty.toml").as_std_path(),
r#"
[rules]
division-by-zero = "ignore"
literal-math-error = "ignore"
"#,
)?;
@@ -1710,12 +1710,12 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
"Expected no diagnostics but got: {diagnostics:#?}"
);
// Enable division-by-zero in the user configuration with warning severity
// Enable literal-math-error in the user configuration with warning severity
update_file(
case.root_path().join("home/.config/ty/ty.toml"),
r#"
[rules]
division-by-zero = "warn"
literal-math-error = "warn"
"#,
)?;
@@ -1753,7 +1753,7 @@ fn changes_to_config_file_override() -> anyhow::Result<()> {
context.join_project_path("ty-override.toml").as_std_path(),
r#"
[rules]
division-by-zero = "ignore"
literal-math-error = "ignore"
"#,
)?;
@@ -1770,12 +1770,12 @@ fn changes_to_config_file_override() -> anyhow::Result<()> {
"Expected no diagnostics but got: {diagnostics:#?}"
);
// Enable division-by-zero in the explicitly specified configuration with warning severity
// Enable literal-math-error in the explicitly specified configuration with warning severity
update_file(
case.project_path("ty-override.toml"),
r#"
[rules]
division-by-zero = "warn"
literal-math-error = "warn"
"#,
)?;

View File

@@ -67,7 +67,7 @@ pub struct Options {
example = r#"
[tool.ty.rules]
possibly-unresolved-reference = "warn"
division-by-zero = "ignore"
literal-math-error = "ignore"
"#
)]
pub rules: Option<Rules>,

View File

@@ -37,8 +37,8 @@ reveal_type(b**b) # revealed: Literal[1]
# Division
reveal_type(a / a) # revealed: float
reveal_type(b / a) # revealed: float
b / b # error: [division-by-zero] "Cannot divide object of type `Literal[False]` by zero"
a / b # error: [division-by-zero] "Cannot divide object of type `Literal[True]` by zero"
b / b # error: [literal-math-error] "Cannot divide object of type `Literal[False]` by zero"
a / b # error: [literal-math-error] "Cannot divide object of type `Literal[True]` by zero"
# bitwise OR
reveal_type(a | a) # revealed: Literal[True]
@@ -57,6 +57,25 @@ reveal_type(a ^ a) # revealed: Literal[False]
reveal_type(a ^ b) # revealed: Literal[True]
reveal_type(b ^ a) # revealed: Literal[True]
reveal_type(b ^ b) # revealed: Literal[False]
# left-shift
reveal_type(a << a) # revealed: Literal[2]
reveal_type(a << b) # revealed: Literal[1]
reveal_type(b << a) # revealed: Literal[0]
reveal_type(b << b) # revealed: Literal[0]
reveal_type(True << 100) # revealed: int
# error: [literal-math-error] "Cannot left shift object of type `Literal[True]` by a negative value"
reveal_type(True << -1) # revealed: int
# right-shift
reveal_type(a >> a) # revealed: Literal[0]
reveal_type(a >> b) # revealed: Literal[1]
reveal_type(b >> a) # revealed: Literal[0]
reveal_type(b >> b) # revealed: Literal[0]
# error: [literal-math-error] "Cannot right shift object of type `Literal[False]` by a negative value"
reveal_type(False >> -1) # revealed: int
```
## Arithmetic with a variable

View File

@@ -39,6 +39,13 @@ def both(x: int):
reveal_type(x // x) # revealed: int
reveal_type(x / x) # revealed: int | float
reveal_type(x % x) # revealed: int
# Edge case where negation leads to overflow:
i64_max = 9223372036854775807
i64_min = -i64_max - 1
reveal_type(i64_max) # revealed: Literal[9223372036854775807]
reveal_type(i64_min) # revealed: Literal[-9223372036854775808]
reveal_type(-i64_min) # revealed: int
```
## Power
@@ -128,16 +135,65 @@ reveal_type(int() / 0) # revealed: int | float
# error: "Cannot divide object of type `Literal[1]` by zero"
reveal_type(1 / False) # revealed: float
# error: [division-by-zero] "Cannot divide object of type `Literal[True]` by zero"
# error: [literal-math-error] "Cannot divide object of type `Literal[True]` by zero"
True / False
# error: [division-by-zero] "Cannot divide object of type `Literal[True]` by zero"
# error: [literal-math-error] "Cannot divide object of type `Literal[True]` by zero"
bool(1) / False
# error: "Cannot divide object of type `float` by zero"
reveal_type(1.0 / 0) # revealed: int | float
# error: "Cannot divide object of type `complex` by zero"
reveal_type(0j / 0) # revealed: int | float | complex
class MyInt(int): ...
# No error for a subclass of int
reveal_type(MyInt(3) / 0) # revealed: int | float
```
## Bit-shifting
Literal arithmetic is supported for bit-shifting operations on `int`s:
```py
reveal_type(42 << 3) # revealed: Literal[336]
reveal_type(0 << 3) # revealed: Literal[0]
reveal_type(-42 << 3) # revealed: Literal[-336]
reveal_type(42 >> 3) # revealed: Literal[5]
reveal_type(0 >> 3) # revealed: Literal[0]
reveal_type(-42 >> 3) # revealed: Literal[-6]
```
If the result of a left shift overflows the `int` literal type, it becomes `int`. Right shifts do
not overflow:
```py
reveal_type(42 << 100) # revealed: int
reveal_type(0 << 100) # revealed: int
reveal_type(-42 << 100) # revealed: int
reveal_type(42 >> 100) # revealed: Literal[0]
reveal_type(0 >> 100) # revealed: Literal[0]
reveal_type(-42 >> 100) # revealed: Literal[-1]
```
It is an error to shift by a negative value. This is handled similarly to `ZeroDivisionError`
detection, above:
```py
# error: [literal-math-error] "Cannot left shift object of type `Literal[42]` by a negative value"
reveal_type(42 << -3) # revealed: int
# error: [literal-math-error]
reveal_type(0 << -3) # revealed: int
# error: [literal-math-error]
reveal_type(-42 << -3) # revealed: int
# error: [literal-math-error] "Cannot right shift object of type `Literal[42]` by a negative value"
reveal_type(42 >> -3) # revealed: int
# error: [literal-math-error]
reveal_type(0 >> -3) # revealed: int
# error: [literal-math-error]
reveal_type(-42 >> -3) # revealed: int
```

View File

@@ -54,6 +54,6 @@ If any of the union elements leads to a division by zero, we will report an erro
```py
def f5(m: int, n: Literal[-1, 0, 1]):
# error: [division-by-zero] "Cannot divide object of type `int` by zero"
# error: [literal-math-error] "Cannot divide object of type `int` by zero"
return m / n
```

View File

@@ -34,9 +34,9 @@ a = test + 3 # ty: ignore[possibly-unresolved-reference]
```py
# error: [unused-ignore-comment]
a = 10 / 2 # ty: ignore[division-by-zero]
a = 10 / 2 # ty: ignore[division-by-zero, unused-ignore-comment]
a = 10 / 2 # ty: ignore[unused-ignore-comment, division-by-zero]
a = 10 / 2 # ty: ignore[literal-math-error]
a = 10 / 2 # ty: ignore[literal-math-error, unused-ignore-comment]
a = 10 / 2 # ty: ignore[unused-ignore-comment, literal-math-error]
a = 10 / 2 # ty: ignore[unused-ignore-comment] # type: ignore
a = 10 / 2 # type: ignore # ty: ignore[unused-ignore-comment]
```
@@ -45,7 +45,7 @@ a = 10 / 2 # type: ignore # ty: ignore[unused-ignore-comment]
```py
# error: [unused-ignore-comment] "Unused `ty: ignore` directive: 'unused-ignore-comment'"
a = 10 / 0 # ty: ignore[division-by-zero, unused-ignore-comment]
a = 10 / 0 # ty: ignore[literal-math-error, unused-ignore-comment]
```
## Multiple unused comments
@@ -54,13 +54,13 @@ Today, ty emits a diagnostic for every unused code. We might want to group the c
some point in the future.
```py
# error: [unused-ignore-comment] "Unused `ty: ignore` directive: 'division-by-zero'"
# error: [unused-ignore-comment] "Unused `ty: ignore` directive: 'literal-math-error'"
# error: [unused-ignore-comment] "Unused `ty: ignore` directive: 'unresolved-reference'"
a = 10 / 2 # ty: ignore[division-by-zero, unresolved-reference]
a = 10 / 2 # ty: ignore[literal-math-error, unresolved-reference]
# error: [unused-ignore-comment] "Unused `ty: ignore` directive: 'invalid-assignment'"
# error: [unused-ignore-comment] "Unused `ty: ignore` directive: 'unresolved-reference'"
a = 10 / 0 # ty: ignore[invalid-assignment, division-by-zero, unresolved-reference]
a = 10 / 0 # ty: ignore[invalid-assignment, literal-math-error, unresolved-reference]
```
## Multiple suppressions
@@ -96,14 +96,14 @@ reveal_type(a) # ty: ignore[revealed-type]
```py
a = 10 / 0 # ty : ignore
a = 10 / 0 # ty: ignore [ division-by-zero ]
a = 10 / 0 # ty: ignore [ literal-math-error ]
```
## Whitespace is optional
```py
# fmt: off
a = 10 / 0 #ty:ignore[division-by-zero]
a = 10 / 0 #ty:ignore[literal-math-error]
```
## Trailing codes comma
@@ -111,13 +111,13 @@ a = 10 / 0 #ty:ignore[division-by-zero]
Trailing commas in the codes section are allowed:
```py
a = 10 / 0 # ty: ignore[division-by-zero,]
a = 10 / 0 # ty: ignore[literal-math-error,]
```
## Invalid characters in codes
```py
# error: [division-by-zero]
# error: [literal-math-error]
# error: [invalid-ignore-comment] "Invalid `ty: ignore` comment: expected a alphanumeric character or `-` or `_` as code"
a = 10 / 0 # ty: ignore[*-*]
```
@@ -127,7 +127,7 @@ a = 10 / 0 # ty: ignore[*-*]
<!-- blacken-docs:off -->
```py
a = 10 / 0 # ty: ignore[division-by-zero]
a = 10 / 0 # ty: ignore[literal-math-error]
# ^^^^^^ trailing whitespace
```
@@ -141,7 +141,7 @@ future.
```py
# error: [unresolved-reference]
# error: [invalid-ignore-comment] "Invalid `ty: ignore` comment: expected a comma separating the rule codes"
a = x / 0 # ty: ignore[division-by-zero unresolved-reference]
a = x / 0 # ty: ignore[literal-math-error unresolved-reference]
```
## Missing closing bracket
@@ -157,7 +157,7 @@ a = x / 2 # ty: ignore[unresolved-reference
An empty codes array suppresses no-diagnostics and is always useless
```py
# error: [division-by-zero]
# error: [literal-math-error]
# error: [unused-ignore-comment] "Unused `ty: ignore` without a code"
a = 4 / 0 # ty: ignore[]
```
@@ -170,9 +170,9 @@ severity: `ty: possibly-undefined-reference=error`
```py
# error: [unused-ignore-comment]
# ty: ignore[division-by-zero]
# ty: ignore[literal-math-error]
a = 4 / 0 # error: [division-by-zero]
a = 4 / 0 # error: [literal-math-error]
```
## Unknown rule
@@ -185,7 +185,7 @@ a = 10 + 4 # ty: ignore[is-equal-14]
## Code with `lint:` prefix
```py
# error:[unknown-rule] "Unknown rule `lint:division-by-zero`. Did you mean `division-by-zero`?"
# error: [division-by-zero]
a = 10 / 0 # ty: ignore[lint:division-by-zero]
# error:[unknown-rule] "Unknown rule `lint:literal-math-error`. Did you mean `literal-math-error`?"
# error: [literal-math-error]
a = 10 / 0 # ty: ignore[lint:literal-math-error]
```

View File

@@ -156,6 +156,6 @@ including module docstrings.
# error: [unused-ignore-comment] "Unused blanket `type: ignore` directive"
# type: ignore
a = 10 / 0 # error: [division-by-zero]
b = a / 0 # error: [division-by-zero]
a = 10 / 0 # error: [literal-math-error]
b = a / 0 # error: [literal-math-error]
```

View File

@@ -520,7 +520,7 @@ if False:
def f():
return
1 / 0 # error: [division-by-zero]
1 / 0 # error: [literal-math-error]
```
### Conflicting type information

View File

@@ -22,7 +22,7 @@ declare_lint! {
///
/// ## Examples
/// ```py
/// a = 20 / 2 # ty: ignore[division-by-zero]
/// a = 20 / 2 # ty: ignore[literal-math-error]
/// ```
///
/// Use instead:
@@ -53,7 +53,7 @@ declare_lint! {
/// Use instead:
///
/// ```py
/// a = 20 / 0 # ty: ignore[division-by-zero]
/// a = 20 / 0 # ty: ignore[literal-math-error]
/// ```
pub(crate) static UNKNOWN_RULE = {
summary: "detects `ty: ignore` comments that reference unknown rules",
@@ -241,7 +241,7 @@ fn check_unused_suppressions(context: &mut CheckSuppressionsContext) {
// This looks silly but it's necessary to check again if a `unused-ignore-comment` is indeed unused
// in case the "unused" directive comes after it:
// ```py
// a = 10 / 2 # ty: ignore[unused-ignore-comment, division-by-zero]
// a = 10 / 2 # ty: ignore[unused-ignore-comment, literal-math-error]
// ```
if context.diagnostics.is_used(suppression.id()) {
continue;

View File

@@ -34,7 +34,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&CONFLICTING_DECLARATIONS);
registry.register_lint(&CONFLICTING_METACLASS);
registry.register_lint(&CYCLIC_CLASS_DEFINITION);
registry.register_lint(&DIVISION_BY_ZERO);
registry.register_lint(&LITERAL_MATH_ERROR);
registry.register_lint(&DUPLICATE_BASE);
registry.register_lint(&DUPLICATE_KW_ONLY);
registry.register_lint(&INSTANCE_LAYOUT_CONFLICT);
@@ -244,17 +244,20 @@ declare_lint! {
declare_lint! {
/// ## What it does
/// It detects division by zero.
/// Detects runtime errors that would result from invalid math operations
/// between two objects with literal `int` types. Examples include division
/// by zero and negative bitshifts.
///
/// ## Why is this bad?
/// Dividing by zero raises a `ZeroDivisionError` at runtime.
/// These math operations will lead to exceptions being raised at runtime.
///
/// ## Examples
/// ```python
/// 5 / 0
/// 5 / 0 # `ZeroDivisionError`
/// 1 << -1 # `ValueError: negative shift count`
/// ```
pub(crate) static DIVISION_BY_ZERO = {
summary: "detects division by zero",
pub(crate) static LITERAL_MATH_ERROR = {
summary: "detects runtime errors such as division by zero or negative bitshifts",
status: LintStatus::preview("1.0.0"),
default_level: Level::Ignore,
}

View File

@@ -89,10 +89,10 @@ use crate::types::call::{Binding, Bindings, CallArgumentTypes, CallArguments, Ca
use crate::types::class::{CodeGeneratorKind, MetaclassErrorKind, SliceLiteral};
use crate::types::diagnostic::{
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO,
INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM,
INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases,
CYCLIC_CLASS_DEFINITION, DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE,
INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION,
INVALID_GENERIC_CLASS, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, LITERAL_MATH_ERROR,
POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics,
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE,
UNSUPPORTED_OPERATOR, report_implicit_return_type, report_instance_layout_conflict,
@@ -1512,42 +1512,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
/// Raise a diagnostic if the given type cannot be divided by zero.
///
/// Expects the resolved type of the left side of the binary expression.
fn check_division_by_zero(
&mut self,
node: AnyNodeRef<'_>,
op: ast::Operator,
left: Type<'db>,
) -> bool {
match left {
Type::BooleanLiteral(_) | Type::IntLiteral(_) => {}
Type::NominalInstance(instance)
if matches!(
instance.class.known(self.db()),
Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool)
) => {}
_ => return false,
}
let (op, by_zero) = match op {
ast::Operator::Div => ("divide", "by zero"),
ast::Operator::FloorDiv => ("floor divide", "by zero"),
ast::Operator::Mod => ("reduce", "modulo zero"),
_ => return false,
};
if let Some(builder) = self.context.report_lint(&DIVISION_BY_ZERO, node) {
builder.into_diagnostic(format_args!(
"Cannot {op} object of type `{}` {by_zero}",
left.display(self.db())
));
}
true
}
fn add_binding(&mut self, node: AnyNodeRef, binding: Definition<'db>, ty: Type<'db>) {
debug_assert!(
binding
@@ -6341,7 +6305,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
(_, Type::Never) => Type::Never,
(ast::UnaryOp::UAdd, Type::IntLiteral(value)) => Type::IntLiteral(value),
(ast::UnaryOp::USub, Type::IntLiteral(value)) => Type::IntLiteral(-value),
(ast::UnaryOp::USub, Type::IntLiteral(value)) => value
.checked_neg()
.map(Type::IntLiteral)
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db())),
(ast::UnaryOp::Invert, Type::IntLiteral(value)) => Type::IntLiteral(!value),
(ast::UnaryOp::UAdd, Type::BooleanLiteral(bool)) => Type::IntLiteral(i64::from(bool)),
@@ -6460,39 +6427,82 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
fn infer_binary_expression_type(
&mut self,
node: AnyNodeRef<'_>,
mut emitted_division_by_zero_diagnostic: bool,
mut emitted_bad_rhs_diagnostic: bool,
left_ty: Type<'db>,
right_ty: Type<'db>,
op: ast::Operator,
) -> Option<Type<'db>> {
// Check for division by zero; this doesn't change the inferred type for the expression, but
// may emit a diagnostic
if !emitted_division_by_zero_diagnostic
&& matches!(
(op, right_ty),
let check_bad_rhs = || {
let lhs_int = match left_ty {
Type::BooleanLiteral(_) | Type::IntLiteral(_) => true,
Type::NominalInstance(instance)
if matches!(
instance.class.known(self.db()),
Some(KnownClass::Int | KnownClass::Bool)
) =>
{
true
}
Type::NominalInstance(instance)
if matches!(
instance.class.known(self.db()),
Some(KnownClass::Float | KnownClass::Complex)
) =>
{
false
}
_ => return false,
};
let (op, by_what) = match (op, lhs_int) {
(ast::Operator::Div, _) => ("divide", "by zero"),
(ast::Operator::FloorDiv, _) => ("floor divide", "by zero"),
(ast::Operator::Mod, _) => ("reduce", "modulo zero"),
(ast::Operator::LShift, true) => ("left shift", "by a negative value"),
(ast::Operator::RShift, true) => ("right shift", "by a negative value"),
_ => return false,
};
if let Some(builder) = self.context.report_lint(&LITERAL_MATH_ERROR, node) {
builder.into_diagnostic(format_args!(
"Cannot {op} object of type `{}` {by_what}",
left_ty.display(self.db())
));
}
true
};
// Check for division by zero or shift by a negative value; this doesn't change the inferred
// type for the expression, but may emit a diagnostic
if !emitted_bad_rhs_diagnostic {
emitted_bad_rhs_diagnostic = match (op, right_ty) {
(
ast::Operator::Div | ast::Operator::FloorDiv | ast::Operator::Mod,
Type::IntLiteral(0) | Type::BooleanLiteral(false)
)
)
{
emitted_division_by_zero_diagnostic = self.check_division_by_zero(node, op, left_ty);
Type::IntLiteral(0) | Type::BooleanLiteral(false),
) => check_bad_rhs(),
(ast::Operator::LShift | ast::Operator::RShift, Type::IntLiteral(n)) if n < 0 => {
check_bad_rhs()
}
_ => false,
};
}
match (left_ty, right_ty, op) {
(Type::Union(lhs_union), rhs, _) => lhs_union.try_map(self.db(), |lhs_element| {
self.infer_binary_expression_type(
node,
emitted_division_by_zero_diagnostic,
emitted_bad_rhs_diagnostic,
*lhs_element,
rhs,
op,
)
}),
(lhs, Type::Union(rhs_union), _) => rhs_union.try_map(self.db(), |rhs_element| {
self.infer_binary_expression_type(
node,
emitted_division_by_zero_diagnostic,
emitted_bad_rhs_diagnostic,
lhs,
*rhs_element,
op,
@@ -6595,6 +6605,22 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Some(Type::IntLiteral(n ^ m))
}
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::LShift) => Some(
u32::try_from(m)
.ok()
.and_then(|m| n.checked_shl(m))
.map(Type::IntLiteral)
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db())),
),
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::RShift) => Some(
u32::try_from(m)
.ok()
.map(|m| n >> m.clamp(0, 63))
.map(Type::IntLiteral)
.unwrap_or_else(|| KnownClass::Int.to_instance(self.db())),
),
(Type::BytesLiteral(lhs), Type::BytesLiteral(rhs), ast::Operator::Add) => {
let bytes = [lhs.value(self.db()), rhs.value(self.db())].concat();
Some(Type::bytes_literal(self.db(), &bytes))
@@ -6661,7 +6687,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
(Type::BooleanLiteral(b1), Type::BooleanLiteral(_) | Type::IntLiteral(_), op) => self
.infer_binary_expression_type(
node,
emitted_division_by_zero_diagnostic,
emitted_bad_rhs_diagnostic,
Type::IntLiteral(i64::from(b1)),
right_ty,
op,
@@ -6669,7 +6695,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
(Type::IntLiteral(_), Type::BooleanLiteral(b2), op) => self
.infer_binary_expression_type(
node,
emitted_division_by_zero_diagnostic,
emitted_bad_rhs_diagnostic,
left_ty,
Type::IntLiteral(i64::from(b2)),
op,

24
ty.schema.json generated
View File

@@ -331,16 +331,6 @@
}
]
},
"division-by-zero": {
"title": "detects division by zero",
"description": "## What it does\nIt detects division by zero.\n\n## Why is this bad?\nDividing by zero raises a `ZeroDivisionError` at runtime.\n\n## Examples\n```python\n5 / 0\n```",
"default": "ignore",
"oneOf": [
{
"$ref": "#/definitions/Level"
}
]
},
"duplicate-base": {
"title": "detects class definitions with duplicate bases",
"description": "## What it does\nChecks for class definitions with duplicate bases.\n\n## Why is this bad?\nClass definitions with duplicate bases raise `TypeError` at runtime.\n\n## Examples\n```python\nclass A: ...\n\n# TypeError: duplicate base class\nclass B(A, A): ...\n```",
@@ -661,6 +651,16 @@
}
]
},
"literal-math-error": {
"title": "detects runtime errors such as division by zero or negative bitshifts",
"description": "## What it does\nDetects runtime errors that would result from invalid math operations\nbetween two objects with literal `int` types. Examples include division\nby zero and negative bitshifts.\n\n## Why is this bad?\nThese math operations will lead to exceptions being raised at runtime.\n\n## Examples\n```python\n5 / 0 # `ZeroDivisionError`\n1 << -1 # `ValueError: negative shift count`\n```",
"default": "ignore",
"oneOf": [
{
"$ref": "#/definitions/Level"
}
]
},
"missing-argument": {
"title": "detects missing required arguments in a call",
"description": "## What it does\nChecks for missing required arguments in a call.\n\n## Why is this bad?\nFailing to provide a required argument will raise a `TypeError` at runtime.\n\n## Examples\n```python\ndef func(x: int): ...\nfunc() # TypeError: func() missing 1 required positional argument: 'x'\n```",
@@ -843,7 +843,7 @@
},
"unknown-rule": {
"title": "detects `ty: ignore` comments that reference unknown rules",
"description": "## What it does\nChecks for `ty: ignore[code]` where `code` isn't a known lint rule.\n\n## Why is this bad?\nA `ty: ignore[code]` directive with a `code` that doesn't match\nany known rule will not suppress any type errors, and is probably a mistake.\n\n## Examples\n```py\na = 20 / 0 # ty: ignore[division-by-zer]\n```\n\nUse instead:\n\n```py\na = 20 / 0 # ty: ignore[division-by-zero]\n```",
"description": "## What it does\nChecks for `ty: ignore[code]` where `code` isn't a known lint rule.\n\n## Why is this bad?\nA `ty: ignore[code]` directive with a `code` that doesn't match\nany known rule will not suppress any type errors, and is probably a mistake.\n\n## Examples\n```py\na = 20 / 0 # ty: ignore[division-by-zer]\n```\n\nUse instead:\n\n```py\na = 20 / 0 # ty: ignore[literal-math-error]\n```",
"default": "warn",
"oneOf": [
{
@@ -913,7 +913,7 @@
},
"unused-ignore-comment": {
"title": "detects unused `type: ignore` comments",
"description": "## What it does\nChecks for `type: ignore` or `ty: ignore` directives that are no longer applicable.\n\n## Why is this bad?\nA `type: ignore` directive that no longer matches any diagnostic violations is likely\nincluded by mistake, and should be removed to avoid confusion.\n\n## Examples\n```py\na = 20 / 2 # ty: ignore[division-by-zero]\n```\n\nUse instead:\n\n```py\na = 20 / 2\n```",
"description": "## What it does\nChecks for `type: ignore` or `ty: ignore` directives that are no longer applicable.\n\n## Why is this bad?\nA `type: ignore` directive that no longer matches any diagnostic violations is likely\nincluded by mistake, and should be removed to avoid confusion.\n\n## Examples\n```py\na = 20 / 2 # ty: ignore[literal-math-error]\n```\n\nUse instead:\n\n```py\na = 20 / 2\n```",
"default": "ignore",
"oneOf": [
{