Here you are
Question from an IDE to the compiler.
I'll be using the term "IDE" for a thing which can semantically analyze source
code and use the results of the analysis to help the user to write code in the
editor. In practical terms, "IDE" is the backend of RLS (rls-analysis) and
IntelliJ Rust.
IDE maintains complete and precise parse tree, so questions can be formulated in
terms of an AST (but it's convenient to use spans/offsets to refer to AST
nodes). That is, IDE will ask "what declaration does this name refer to", but it
won't ask "where are the references in this file" (it already knows) or "what
attributes are available on this function" (it's easy to calculate
syntactically).
Collect Errors
What are the compiler errors in file "foo/bar/baz.rs"?
This query is needed to show errors as the user types.
I think it's important to restrict the scope of the query to a single file
(errors on demand), to avoid checking the majority of the project, which can be
costly (O(size of the project)) even with fully incremental compilation.
This query can be fully async and not very fast, as it does not block any user
interaction.
From the user's POV, every error should be accompanied with a quick fix: an
action to automatically fix errors, perhaps with some additional input from the
user. In the ideal world, we would like to keep errors in the compiler and quick
fixes in the IDE (that is, we want to free the compiler from the knowledge of
how to edit and refactor code). It's not obvious how to achieve this separation
though: some information which is needed to propose a fix is available just when
compiler decides that there's an error.
Resolve Reference
What definition does this reference points to?
I expect this to be the most popular query. IDE will usually ask about all the
references in a single file (to do highlighting) and will often ask about single
references here and there in different files all over the project. This query
must be as fast as possible. Ideally, on the IDE side we would love to see a
synchronous api like reference.resolve() -> Option<Declaration>
.
This query should also handle ambiguity. If the name is ambiguous (the name can
mean two different method from different traits, and UFCS is needed), it should
be possible to get all possible targets: reference.multi_resolve() -> Vec<Declaration>
. This should also handle use declarations, which can imoprt a
name from different namespaces.
Note that the compiler does not need to handle the reverse query. That is, "find
usages" can (and probably should) be handled by the IDE. The IDE maintains a
text index of all references in project (cheap to maintain, because it depends
only on the syntactic contents of a single file). For find usages, IDE finds the
list of candidates using this text index, and then filters the candidates
invoking compiler's "Resolve Reference" query (it should be fast, and again even
for "random access" it should be possible to avoid analyzing the majority of the
project).
Collect Visible Names
What can be the name of this reference such that it resolves to anything?
That is, for the code
struct S;
impl S {
fn foo(&self) {}
fn bar(&self) {}
}
fn f(s: S) {
s.foo // <- `.foo` is the reference we will be asking about
}
the answer should be [foo, bar]
. Basically, this question gives completion
variants without filtering by actual reference name. It can be seen as a
more general version of the "Resolve Reference" query. In fact, in IntelliJ
resolve reference is usually implemented as "Collect Visible Names" followed by
filtering by the actual method name.
This question will be asked about at most one identifier in the file, it should
be reasonably fast, but can be async (ideally, some names are available
immediately, and the others are supplied in the async fashion).
What If Query
The queries should be available in the "what if" mode. That is, IDE should be
able to inject some Rust code fragment anywhere into the existing project, and
ask questions about it. It can be trivially implemented as "modify file, ask
question, rollback modification", but it makes sense to build in some support
for this, because you can avoid a lot of invalidation work (and "what if"
queries may be asked in the context that blocks user's actions, so they need to
be as fast as possible).
An example of what if query would be a completion request for the following
code, with the caret just after the dot.
fn f(s: S) {
s.
}
Here, the IDE can't ask "Collect Visible Names" query, because there's no
reference yet. So, IDE inserts a dummy identifier
fn f(s: S) {
s.rust_rulezz
}
and asks "Collect Visible Names" for it.
Type related queries
What is the type of this expression?
Does this type implements this trait?
These queries will be a little awkward because the answer is not just some node
in the existing AST, but some representation of the thing that exists only in
compiler's memory.
I am not sure about What traits are implemented by this type query. This is
a bad type of "search" query, which requires looking at the whole project, but
looks like it must be implemented in the compiler anyway to do "Collect visible
names" (last time I looked, the compiler filtered the candidate traits by the
name of the method resolved, but this won't work for completion).