-
Notifications
You must be signed in to change notification settings - Fork 1.8k
[ty] Add new hover mdtest assertion
#20786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
37effea
be47fbe
11e5ecb
6e73b85
5c75e91
9d8e35b
6370aea
319f5be
19600ec
ab26136
ac1b68c
35a5fd7
35b568d
0198857
51d5bc7
93db883
8221450
c3626a6
a2eaf7c
8c12fcb
c574dff
6d1c549
b683da8
adf58b6
180d9de
4177246
847e5f0
b18d213
eaba8bc
6ddf729
c08a2d6
8426cc6
7b88440
39353da
1810b7c
fbc211f
b438631
34b463d
0817a36
eb95e49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| # Hover type assertions | ||
|
|
||
| You can use the `hover` assertion to test the inferred type of an expression. This exercises the | ||
| same logic as the hover LSP action. | ||
|
|
||
| Typically, you will not need to use the `hover` action to test the behavior of our type inference | ||
| code, since you can also use `reveal_type` to display the inferred type of an expression. Since | ||
| `reveal_type` is part of the standard library, we prefer to use it when possible. | ||
|
|
||
| However, there are certain situations where `reveal_type` and `hover` will give different results. | ||
| In particular, `reveal_type` is not transparent to bidirectional type checking, as seen in the | ||
| "Different results" section below. | ||
|
|
||
| ## Syntax | ||
|
|
||
| ### Basic syntax | ||
|
|
||
| The `hover` assertion operates on a specific location in the source text. We find the "inner-most" | ||
| expression at that position, and then query the inferred type of that expression. The row to query | ||
| is identified just like any other mdtest assertion. The column to query is identified by a down | ||
| arrow (↓) in the assertion. (Note that the down arrow should always appear immediately before the | ||
| `hover` keyword in the assertion.) | ||
|
|
||
| ```py | ||
| def test_basic_types(parameter: int) -> None: | ||
| # ↓ hover: int | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I feel like the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is the year 2025, Alex. Let us have some nice things.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I had worried about that part. I went with the down arrow partly because I agree with David's half-snark, and partly because I thought that it was more important to optimize here for readability than writability, and the down arrow is visually much clearer.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hmmm, I'm not sure I agree. One of the best things about mdtest is how readable it makes our tests, but another of the best things about mdtest is how low-friction it is to write new tests. It's so easy to add new assertions to an existing mdtest suite; it doesn't come with nearly as much boilerplate as writing all our unit tests in Rust or even Python would. And I think that's resulted in us writing lots and lots of assertions, and having a very robustly tested type checker -- great things happen when you make it easy to write tests!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still prefer the down arrow over an ASCII character for this. It is much clearer in intent, and even if you have to fall back on copy/paste, I don't feel like that would be as much of a deterrent to writing these assertions as you suggest. (I completely agree with your argument when talking about mdtest existing in the first place — the marginal ease of writing these over the previous Rust tests is huge! But I don't think that argument applies with the same force when considering something like this.)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be an emoji then? ⬇️ |
||
| parameter | ||
|
|
||
| # ↓ hover: Literal[10] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have a good suggestion as an alternative, but a downside of the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see #20786 (comment) :-) |
||
| number = 10 | ||
|
|
||
| # ↓ hover: Literal["hello"] | ||
| text = "hello" | ||
| ``` | ||
|
|
||
| ### Multiple hovers on the same line | ||
|
|
||
| We can have multiple hover assertions for different positions on the same line: | ||
|
|
||
| ```py | ||
| # ↓ hover: Literal[1] | ||
| # ↓ hover: Literal[2] | ||
| # ↓ hover: Literal[3] | ||
| total = 1 + 2 + 3 | ||
|
|
||
| # ↓ hover: Literal[5] | ||
| # ↓ hover: Literal[3] | ||
| result = max(5, 3) | ||
| ``` | ||
|
|
||
| ### Hovering works on every character in an expression | ||
|
|
||
| ```py | ||
| def _(param: bool) -> None: | ||
| # ↓ hover: bool | ||
| # ↓ hover: bool | ||
| # ↓ hover: bool | ||
| # ↓ hover: bool | ||
| # ↓ hover: bool | ||
| result = param | ||
| ``` | ||
|
|
||
| ### Hovering with unicode characters | ||
|
|
||
| ```py | ||
| def _(café: str) -> None: | ||
| # ↓ hover: str | ||
| # ↓ hover: str | ||
| # ↓ hover: str | ||
| # ↓ hover: str | ||
| result = café | ||
| ``` | ||
|
|
||
| ## Different results for `reveal_type` and `hover` | ||
|
|
||
| ```py | ||
| from typing import overload | ||
|
|
||
| def f(x: dict[str, int]) -> None: ... | ||
|
|
||
| # revealed: dict[Unknown, Unknown] | ||
| f(reveal_type({})) | ||
|
|
||
| # ↓ hover: dict[str, int] | ||
| f({}) | ||
| ``` | ||
|
Comment on lines
+74
to
+86
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example confuses me somewhat, because the introduction to this mdtest suite states that
But
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As of #20635, we're doing bidirectional type checking for call arguments, and using the parameter annotation as the type context. And as of #20523, we're using the type context to infer a more precise type for dictionary literals. So That might suggest that we want to propagate the type context through these kinds of inner calls (not just for
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That sounds like it might make sense to me... It seems quite counterintuitive to me that hovering over the first from typing import reveal_type
def needs_list_of_strings(x: list[str | None]): ...
def identity[T](x: T) -> T:
return x
needs_list_of_strings([""])
needs_list_of_strings(identity([""]))https://play.ty.dev/53299738-b406-4e46-ba40-c0240225f0ae
Fair enough! The additional code in
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Thanks BTW -- I hadn't fully internalised that this was the consequence of those changes! Makes sense. |
||
|
|
||
| ## Hovering on different expression types | ||
|
|
||
| ### Literals | ||
|
|
||
| ```py | ||
| # ↓ hover: Literal[42] | ||
| int_value = 42 | ||
|
|
||
| # ↓ hover: Literal["test"] | ||
| string_value = "test" | ||
|
|
||
| # ↓ hover: Literal[True] | ||
| bool_value = True | ||
| ``` | ||
|
|
||
| ### Names and attributes | ||
|
|
||
| ```py | ||
| class MyClass: | ||
| value: int | ||
|
|
||
| def test_attributes(instance: MyClass) -> None: | ||
| # ↓ hover: MyClass | ||
| instance | ||
|
|
||
| # ↓ hover: int | ||
| instance.value | ||
| ``` | ||
|
|
||
| ### Function definitions | ||
|
|
||
| ```py | ||
| def f(x: int) -> None: ... | ||
|
|
||
| # ↓ hover: def f(x: int) -> None | ||
| result = f | ||
| ``` | ||
|
|
||
| ### Binary operations | ||
|
|
||
| ```py | ||
| # ↓ hover: Literal[10] | ||
| # ↓ hover: Literal[20] | ||
| result = 10 + 20 | ||
| ``` | ||
|
|
||
| ### Comprehensions | ||
|
|
||
| ```py | ||
| # List comprehension | ||
| # ↓ hover: list[@Todo(list comprehension element type)] | ||
| result = [x for x in range(5)] | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these tests be in
ty_ide? I'd find it confusing that a change inty_idecan break a test inty_python_semantic. It would probably take me a while to realize that a test inty_python_semanticisn't failing because of a change inty_python_semantic(which would be the first place where I go look), but because of a change inty_ide.