[ty] highlight the argument in static_assert error messages (#19426)

Closes https://github.com/astral-sh/ty/issues/209.

Before:
```
error[static-assert-error]: Static assertion error: custom message
 --> test.py:2:1
  |
1 | from ty_extensions import static_assert
2 | static_assert(3 > 4, "custom message")
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
```

After:
```
error[static-assert-error]: Static assertion error: custom message
 --> test.py:2:1
  |
1 | from ty_extensions import static_assert
2 | static_assert(3 > 4, "custom message")
  | ^^^^^^^^^^^^^^-----^^^^^^^^^^^^^^^^^^^
  |               |
  |               Inferred type of argument is `Literal[False]`
  |
```
This commit is contained in:
Jack O'Connor 2025-07-23 08:24:12 -07:00 committed by GitHub
parent 5a55bab3f3
commit 88bd82938f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 193 additions and 6 deletions

View file

@ -1,5 +1,7 @@
# `cast` # `cast`
## Behavior
`cast()` takes two arguments, one type and one value, and returns a value of the given type. `cast()` takes two arguments, one type and one value, and returns a value of the given type.
The (inferred) type of the value and the given type do not need to have any correlation. The (inferred) type of the value and the given type do not need to have any correlation.
@ -78,3 +80,15 @@ def f(x: Any, y: Unknown, z: Any | str | int):
e = cast(str | int | Any, z) # error: [redundant-cast] e = cast(str | int | Any, z) # error: [redundant-cast]
``` ```
## Diagnostic snapshots
<!-- snapshot-diagnostics -->
```py
import secrets
from typing import cast
# error: [redundant-cast] "Value is already of type `int`"
cast(int, secrets.randbelow(10))
```

View file

@ -0,0 +1,34 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: cast.md - `cast` - Diagnostic snapshots
mdtest path: crates/ty_python_semantic/resources/mdtest/directives/cast.md
---
# Python source files
## mdtest_snippet.py
```
1 | import secrets
2 | from typing import cast
3 |
4 | # error: [redundant-cast] "Value is already of type `int`"
5 | cast(int, secrets.randbelow(10))
```
# Diagnostics
```
warning[redundant-cast]: Value is already of type `int`
--> src/mdtest_snippet.py:5:1
|
4 | # error: [redundant-cast] "Value is already of type `int`"
5 | cast(int, secrets.randbelow(10))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
info: rule `redundant-cast` is enabled by default
```

View file

@ -0,0 +1,104 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: type_api.md - Type API (`ty_extensions`) - Diagnostic snapshots
mdtest path: crates/ty_python_semantic/resources/mdtest/type_api.md
---
# Python source files
## mdtest_snippet.py
```
1 | from ty_extensions import static_assert
2 | import secrets
3 |
4 | # a passing assert
5 | static_assert(1 < 2)
6 |
7 | # evaluates to False
8 | # error: [static-assert-error]
9 | static_assert(1 > 2)
10 |
11 | # evaluates to False, with a message as the second argument
12 | # error: [static-assert-error]
13 | static_assert(1 > 2, "with a message")
14 |
15 | # evaluates to something falsey
16 | # error: [static-assert-error]
17 | static_assert("")
18 |
19 | # evaluates to something ambiguous
20 | # error: [static-assert-error]
21 | static_assert(secrets.randbelow(2))
```
# Diagnostics
```
error[static-assert-error]: Static assertion error: argument evaluates to `False`
--> src/mdtest_snippet.py:9:1
|
7 | # evaluates to False
8 | # error: [static-assert-error]
9 | static_assert(1 > 2)
| ^^^^^^^^^^^^^^-----^
| |
| Inferred type of argument is `Literal[False]`
10 |
11 | # evaluates to False, with a message as the second argument
|
info: rule `static-assert-error` is enabled by default
```
```
error[static-assert-error]: Static assertion error: with a message
--> src/mdtest_snippet.py:13:1
|
11 | # evaluates to False, with a message as the second argument
12 | # error: [static-assert-error]
13 | static_assert(1 > 2, "with a message")
| ^^^^^^^^^^^^^^-----^^^^^^^^^^^^^^^^^^^
| |
| Inferred type of argument is `Literal[False]`
14 |
15 | # evaluates to something falsey
|
info: rule `static-assert-error` is enabled by default
```
```
error[static-assert-error]: Static assertion error: argument of type `Literal[""]` is statically known to be falsy
--> src/mdtest_snippet.py:17:1
|
15 | # evaluates to something falsey
16 | # error: [static-assert-error]
17 | static_assert("")
| ^^^^^^^^^^^^^^--^
| |
| Inferred type of argument is `Literal[""]`
18 |
19 | # evaluates to something ambiguous
|
info: rule `static-assert-error` is enabled by default
```
```
error[static-assert-error]: Static assertion error: argument of type `int` has an ambiguous static truthiness
--> src/mdtest_snippet.py:21:1
|
19 | # evaluates to something ambiguous
20 | # error: [static-assert-error]
21 | static_assert(secrets.randbelow(2))
| ^^^^^^^^^^^^^^--------------------^
| |
| Inferred type of argument is `int`
|
info: rule `static-assert-error` is enabled by default
```

View file

@ -266,6 +266,34 @@ shouted_message = "A custom message".upper()
static_assert(False, shouted_message) static_assert(False, shouted_message)
``` ```
## Diagnostic snapshots
<!-- snapshot-diagnostics -->
```py
from ty_extensions import static_assert
import secrets
# a passing assert
static_assert(1 < 2)
# evaluates to False
# error: [static-assert-error]
static_assert(1 > 2)
# evaluates to False, with a message as the second argument
# error: [static-assert-error]
static_assert(1 > 2, "with a message")
# evaluates to something falsey
# error: [static-assert-error]
static_assert("")
# evaluates to something ambiguous
# error: [static-assert-error]
static_assert(secrets.randbelow(2))
```
## Type predicates ## Type predicates
The `ty_extensions` module also provides predicates to test various properties of types. These are The `ty_extensions` module also provides predicates to test various properties of types. These are

View file

@ -1264,28 +1264,35 @@ impl KnownFunction {
if truthiness.is_always_true() { if truthiness.is_always_true() {
return; return;
} }
if let Some(message) = message let mut diagnostic = if let Some(message) = message
.and_then(Type::into_string_literal) .and_then(Type::into_string_literal)
.map(|s| s.value(db)) .map(|s| s.value(db))
{ {
builder.into_diagnostic(format_args!("Static assertion error: {message}")); builder.into_diagnostic(format_args!("Static assertion error: {message}"))
} else if *parameter_ty == Type::BooleanLiteral(false) { } else if *parameter_ty == Type::BooleanLiteral(false) {
builder.into_diagnostic( builder.into_diagnostic(
"Static assertion error: argument evaluates to `False`", "Static assertion error: argument evaluates to `False`",
); )
} else if truthiness.is_always_false() { } else if truthiness.is_always_false() {
builder.into_diagnostic(format_args!( builder.into_diagnostic(format_args!(
"Static assertion error: argument of type `{parameter_ty}` \ "Static assertion error: argument of type `{parameter_ty}` \
is statically known to be falsy", is statically known to be falsy",
parameter_ty = parameter_ty.display(db) parameter_ty = parameter_ty.display(db)
)); ))
} else { } else {
builder.into_diagnostic(format_args!( builder.into_diagnostic(format_args!(
"Static assertion error: argument of type `{parameter_ty}` \ "Static assertion error: argument of type `{parameter_ty}` \
has an ambiguous static truthiness", has an ambiguous static truthiness",
parameter_ty = parameter_ty.display(db) parameter_ty = parameter_ty.display(db)
)); ))
} };
diagnostic.annotate(
Annotation::secondary(context.span(&call_expression.arguments.args[0]))
.message(format_args!(
"Inferred type of argument is `{}`",
parameter_ty.display(db)
)),
);
} }
} }