mirror of
https://github.com/astral-sh/ruff
synced 2026-01-06 06:04:16 -05:00
## Summary This PR proposes that we add a new `set_concise_message` functionality to our `Diagnostic` construction API. When used, the concise message that is otherwise auto-generated from the main diagnostic message and the primary annotation will be overwritten with the custom message. To understand why this is desirable, let's look at the `invalid-key` diagnostic. This is how I *want* the full diagnostic to look like: <img width="620" height="282" alt="image" src="https://github.com/user-attachments/assets/3bf70f52-9d9f-4817-bc16-fb0ebf7c2113" /> However, without the change in this PR, the concise message would have the following form: ``` error[invalid-key]: Unknown key "Age" for TypedDict `Person`: Unknown key "Age" - did you mean "age"? ``` This duplication is why the full `invalid-key` diagnostic used a main diagnostic message that is only "Invalid key for TypedDict `Person`", to make that bearable: ``` error[invalid-key] Invalid key for TypedDict `Person`: Unknown key "Age" - did you mean "age"? ``` This is still less than ideal, *and* we had to make the "full" diagnostic worse. With the new API here, we have to make no such compromises. We need to do slightly more work (provide one additional custom-designed message), but we get to keep the "full" diagnostic that we actually want, and we can make the concise message more terse and readable: ``` error[invalid-key] Unknown key "Age" for TypedDict `Person` - did you mean "age"? ``` Similar problems exist for other diagnostics as well (I really want this for https://github.com/astral-sh/ruff/pull/21476). In this PR, I only changed `invalid-key` and `type-assertion-failure`. The PR here is somewhat related to the discussion in https://github.com/astral-sh/ty/issues/1418, but note that we are solving a problem that is unrelated to sub-diagnostics. ## Test Plan Updated tests
3.2 KiB
3.2 KiB
assert_never
Basic functionality
assert_never makes sure that the type of the argument is Never.
Correct usage
from typing_extensions import assert_never, Never, Any
from ty_extensions import Unknown
def _(never: Never):
assert_never(never) # fine
Diagnostics
If it is not, a type-assertion-failure diagnostic is emitted.
from typing_extensions import assert_never, Never, Any
from ty_extensions import Unknown
def _():
assert_never(0) # error: [type-assertion-failure]
def _():
assert_never("") # error: [type-assertion-failure]
def _():
assert_never(None) # error: [type-assertion-failure]
def _():
assert_never(()) # error: [type-assertion-failure]
def _(flag: bool, never: Never):
assert_never(1 if flag else never) # error: [type-assertion-failure]
def _(any_: Any):
assert_never(any_) # error: [type-assertion-failure]
def _(unknown: Unknown):
assert_never(unknown) # error: [type-assertion-failure]
Use case: Type narrowing and exhaustiveness checking
[environment]
python-version = "3.10"
assert_never can be used in combination with type narrowing as a way to make sure that all cases
are handled in a series of isinstance checks or other narrowing patterns that are supported.
from typing_extensions import assert_never, Literal
class A: ...
class B: ...
class C: ...
def if_else_isinstance_success(obj: A | B):
if isinstance(obj, A):
pass
elif isinstance(obj, B):
pass
elif isinstance(obj, C):
pass
else:
assert_never(obj)
def if_else_isinstance_error(obj: A | B):
if isinstance(obj, A):
pass
# B is missing
elif isinstance(obj, C):
pass
else:
# error: [type-assertion-failure] "Type `B & ~A & ~C` is not equivalent to `Never`"
assert_never(obj)
def if_else_singletons_success(obj: Literal[1, "a"] | None):
if obj == 1:
pass
elif obj == "a":
pass
elif obj is None:
pass
else:
assert_never(obj)
def if_else_singletons_error(obj: Literal[1, "a"] | None):
if obj == 1:
pass
elif obj is "A": # "A" instead of "a"
pass
elif obj is None:
pass
else:
# error: [type-assertion-failure] "Type `Literal["a"]` is not equivalent to `Never`"
assert_never(obj)
def match_singletons_success(obj: Literal[1, "a"] | None):
match obj:
case 1:
pass
case "a":
pass
case None:
pass
case _ as obj:
assert_never(obj)
def match_singletons_error(obj: Literal[1, "a"] | None):
match obj:
case 1:
pass
case "A": # "A" instead of "a"
pass
case None:
pass
case _ as obj:
# TODO: We should emit an error here, but the message should
# show the type `Literal["a"]` instead of `@Todo(…)`. We only
# assert on the first part of the message because the `@Todo`
# message is not available in release mode builds.
# error: [type-assertion-failure] "Type `@Todo"
assert_never(obj)