[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`
## Behavior
`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.
@ -78,3 +80,15 @@ def f(x: Any, y: Unknown, z: Any | str | int):
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)
```
## 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
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() {
return;
}
if let Some(message) = message
let mut diagnostic = if let Some(message) = message
.and_then(Type::into_string_literal)
.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) {
builder.into_diagnostic(
"Static assertion error: argument evaluates to `False`",
);
)
} else if truthiness.is_always_false() {
builder.into_diagnostic(format_args!(
"Static assertion error: argument of type `{parameter_ty}` \
is statically known to be falsy",
parameter_ty = parameter_ty.display(db)
));
))
} else {
builder.into_diagnostic(format_args!(
"Static assertion error: argument of type `{parameter_ty}` \
has an ambiguous static truthiness",
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)
)),
);
}
}