-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[ty] Implement goto-type for inlay type hints #21533
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
Conversation
|
Have had this bee in my bonnet all week the IDE feels SO much better with these being clickable! |
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
| f.with_detail(TypeDetail::Type(Type::ClassLiteral(self.class))) | ||
| .write_str(self.class.name(self.db))?; |
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.
This is the single-most important type span we introduce, since it's nearly every nominal type's name.
|
| write!( | ||
| f, | ||
| "bound method {instance}.{method}{type_parameters}{signature}", | ||
| method = function.name(self.db), | ||
| instance = self_ty.display_with(self.db, self.settings.singleline()), | ||
| type_parameters = type_parameters, | ||
| signature = signature | ||
| .bind_self(self.db, Some(typing_self_ty)) | ||
| .display_with(self.db, self.settings.clone()) | ||
| ) | ||
| f.write_str("bound method ")?; | ||
| self_ty | ||
| .display_with(self.db, self.settings.singleline()) | ||
| .fmt_detailed(f)?; | ||
| f.write_char('.')?; | ||
| f.with_detail(TypeDetail::Type(self.ty)) | ||
| .write_str(function.name(self.db))?; | ||
| type_parameters.fmt_detailed(f)?; | ||
| signature | ||
| .bind_self(self.db, Some(typing_self_ty)) | ||
| .display_with(self.db, self.settings.clone()) | ||
| .fmt_detailed(f) |
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.
A lot of the PR is me breaking up a nice pretty write! into individual parts to invoke fmt_detailed where appropriate.
| impl<'db> TupleSpec<'db> { | ||
| pub(crate) fn display_with( | ||
| &'db self, | ||
| pub(crate) fn display_with<'a>( | ||
| &'a self, | ||
| db: &'db dyn Db, | ||
| settings: DisplaySettings<'db>, | ||
| ) -> DisplayTuple<'db> { | ||
| ) -> DisplayTuple<'a, 'db> { |
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.
Deconflating these lifetimes was unfortunately loadbearing. I had to do that a lot.
|
|
||
| /// Internal method to write signature with the signature writer | ||
| fn write_signature(&self, writer: &mut SignatureWriter) -> fmt::Result { | ||
| fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result { | ||
| // Immediately write a marker signaling we're starting a signature | ||
| let _ = f.with_detail(TypeDetail::SignatureStart); | ||
| // When we exit this function, write a marker signaling we're ending a signature | ||
| let mut f = f.with_detail(TypeDetail::SignatureEnd); |
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.
An incredible victory for not wanting to have to reverse-engineer what parameters belong to nested signatures.
MichaReiser
left a comment
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.
The implementation looks good to me. I've one suggestion that should help to reduce the necessary changes because of Join.
I do find the test very confusing as we now see go to targets in places where we don't show any inlay hints. I think we should change this before landing this PR
|
|
||
| impl Drop for TypeDetailGuard<'_, '_, '_, '_> { | ||
| fn drop(&mut self) { | ||
| // The fallibility here is primarily retrieving `TypeWriter::Details` |
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 we bail early if std::thread::panicking?
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.
I don't think that's necessary?
| let hints = inlay_hints(&self.db, self.file, self.range, settings); | ||
|
|
||
| let mut buf = source_text(&self.db, self.file).as_str().to_string(); | ||
|
|
||
| let mut diagnostics = Vec::new(); | ||
| let mut tbd_diagnostics = Vec::new(); |
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.
Are those to be defined diagnostics?
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.
I have to gather them up transiently while we create the annotated file, and then create the annotated file, and then create them properly.
|
Micha you're god damn incredible, the join impl Just Worked. |
AlexWaygood
left a comment
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.
Very cool!
| | Type::BytesLiteral(_) | ||
| | Type::EnumLiteral(_) => { | ||
| write!(f, "Literal[{representation}]") | ||
| f.write_str("Literal[")?; |
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.
It would be cool if we could also have goto-definition for the Literal part of e.g. Literal[42]. We don't have any documentation for typing.Literal in our vendored typeshed stubs right now (because we currently only add docstrings for classes, functions and modules), but it would be nice to add that capability to docstring-adder at some point. Then when you goto definition for the symbol Literal you'd see all of this lovely explanation in the stub file to tell you how Literal types work (this is the docstring for the symbol at runtime):
% uvx python -m pydoc typing.Literal
Help on _TypedCacheSpecialForm in typing:
typing.Literal = typing.Literal
Special typing form to define literal types (a.k.a. value types).
This form can be used to indicate to type checkers that the corresponding
variable or function parameter has a value equivalent to the provided
literal (or one of several literals)::
def validate_simple(data: Any) -> Literal[True]: # always returns True
...
MODE = Literal['r', 'rb', 'w', 'wb']
def open_helper(file: str, mode: MODE) -> str:
...
open_helper('/some/path', 'r') # Passes type check
open_helper('/other/path', 'typo') # Error in type checker
Literal[...] cannot be subclassed. At runtime, an arbitrary value
is allowed as type argument to Literal[...], but type checkers may
impose restrictions.
The symbol typing.Literal itself has type Type::SpecialForm(SpecialFormType::Literal) in our model, so would you do that like this?
| f.write_str("Literal[")?; | |
| f.with_detail(TypeDetail::Type(Type::SpecialForm(SpecialFormType::Literal))).write_str("Literal")?; | |
| f.write_char('[')?; |
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.
I know you literally gave me the implementation but I'd like to keep these things to followups (I'm terrified of this PR getting merge conflicted to hell).
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.
np
| match self.ty { | ||
| Type::Dynamic(dynamic) => dynamic.fmt(f), | ||
| Type::Never => f.write_str("Never"), | ||
| Type::Dynamic(dynamic) => write!(f, "{dynamic}"), |
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.
if the type is Type::Dynamic(DynamicType::Any), it would be cool if we could goto the definition for the Any symbol (with has type Type::SpecialForm(SpecialFormType::Any) in our model)
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.
(similarly leaving as a followup)
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.
Could you also add tests for generic-alias types, e.g.
class Foo[T]: ...
a [: <class 'Foo[int]'>] = Foo[int]and subclass-of types, e.g.
def f(x: list[str]):
y [: type[list[str]]] = type(x)For the first one I'd expect goto targets for both Foo and int in <class 'Foo[int]>. For the second one I'd expect goto targets for both list and str in type[list[str]]
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.
Cool with this also being a followup?
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.
of course!
|
looks like you'll need to add some snapshot filters to get snapshots that include filepaths working on Windows 🙃 |
|
Ughhhh This is really annoying because |
|
Actually I'm extremely confused why this isn't a problem in any of the goto tests |
|
(More followups for the followup pile I guess) |
|
@Gankra, Sick! |
This PR generalizes the signature_help system's SignatureWriter which could get the subspans of function parameters.
We now have TypeDetailsWriter which is threaded between type's display implementations via a new
fmt_detailedmethod that many of the Display types now have.With this information we can properly add goto-type targets to our inlay hints. This also lays groundwork for any future "I want to render a type but get spans" work.
Also a ton of lifetimes are introduced to avoid things getting conflated with
'db.This PR is broken up into a series of commits:
SignatureWritertoTypeDetailsWriter, but not using it anywhere else. This commit was confirmed to be a non-functional change (no test results changed)fmt_detailedeverywhere to thread throughTypeDetailsWriterand annotate various spans as "being" a given Type -- this is also where I had to reckon with a ton of erroneous&'db self. This commit was also confirmed to be a non-functional change.