-
Notifications
You must be signed in to change notification settings - Fork 3.4k
More context on diagnostics #12583
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
Comments
@scohen wouldnt it be better to include the column in the diagnostic? This way you know exactly where to fix it? |
Right now, due to the other issue that I'm not remembering (the one where warnings are just printed to the console rather than being returned) I have to parse out the line number and variable name from a string, so that compounds the problem. In this case, I think that using string replacements is much less safe than using an AST transform. One of the nicest things about elixir is the ability to change code with code, which enables a much more reliable transformation. I'm trying to build a language server that provides abstractions around doing transformations like this, and I would much rather that extensions deal with transforming AST rather than using string manipulation. Replacing variables with and underscored version is only the beginning here, I'd like to include refactoring support as well, and having two ways to do code modification (AST and string manipulation) doesn't seem ideal to me. |
For example, the entire code replacement for the above is just this: underscored_variable_name = :"_#{unused_variable_name}"
Macro.postwalk(quoted_ast, fn
{^unused_variable_name, meta, nil} ->
{underscored_variable_name, meta, nil}
other ->
other
end)
|> Macro.to_string() That's pretty clean. |
Got it. Elixir v1.15 has Code.with_diagnostics for capturing the warning. The parsing is meant to be tackled in #11967. :) |
@scohen said: I see how this gets tricky; Tuples have a different AST representation than something like a map. In this case, I feel what you have there should be equivalent to what I'd desire (but with a cursor in there) Same with the do/end. I really only care about the things before the cursor, and I would likely be feeding def foo do
{foo, to the function. I would say that the cursor position is very relevant to me. {:def, [line: 1],
[{:foo, [line: 1], nil},
[do:
{:foo, {:__cursor__, [line: 2, column: 8], []}]]} |
I think I am missing a couple things:
|
I think there are two conflicting things at work here. To answer your questions directly:
Not really, if I have a warning on a line, I really only need the AST from that line to fix the warning
If I read this correctly, I think this would be along the lines of a "rename variable" kind of fix. In that case, yes, I would need more of the AST so that I could anticipate scoping correctly. In that case, I'd likely need the entire file, which is definitely possible. |
I am not convinced this will work. Elixir has no tooling to print incomplete code. Imagine you have a three element tuple and the one in the middle needs to be fixed. How are you going to amend the AST up to the cursor and join it with the rest of the file? The AST stitching would be very complex. Alternatively you could also print the partial AST only up to said line, but then you would have to print an incomplete tuple, which is also not an option. so I don’t see how this could be done without the full AST. And even the full AST comes with the pitfall that you will format the user code. in this case, I think a textual fix is the simpler. And other workflows could use the AST fix. |
I'm really confused by your response. I was answering the question of if I need to complete AST in order to replace an unused variable on a specific line. By definition, I only need the one line (my assumption is that an unused variable can only appear on one line). In my above example, I would use the context at the end of the line, for reasons you pointed out. |
I would really prefer to present one interface to the implementers of code actions. Knowing when and how to switch between the two will make building for the project a lot more difficult. |
What I am saying is that this code (#12583 (comment)) with a partial AST won’t ever work or have many pitfalls. For example, imagine you have this:
And var2 is unused. The AST would be:
Even if you rename var2 to _var2, there is no way for you to say: “print it only until var2, not closing any of the parens or tuple along the way”. And doing so would be complex. And even if we give a partial or complete AST, you can still have “var2 = :foo” just before that definition which you don’t want to rename. Given the input above, can you tell me what are the inputs and outputs of all of the steps you outlined in your original comment? |
You're saying that I'm doing something that I'm not doing ;). I'm not printing out a subset of the line. I'm taking the entire line's AST, and running it through a replacement function, then I emit the line (and yes, it will emit an end block, which I remove). I then do a textual diff against the original line and emit that as an edit. The example you cite above works perfectly in lexical. This approach falls apart for those circumstances when the single line can't be parsed. That's what I'm asking for help with. |
Can you give a concrete example? |
I don’t think we will ever be able to parse a single line. For example, how to parse the second line here:
and then, how to format the second line which can be incomplete in all kinds of ways? even building the AST for a partial code would be very hard. cursor parsing requires all code up to the cursor, and that’s what the assumption was based on. And even that is hard per the above. |
Another way to put it: it is impossible to parse a single line. :) so that can’t happen. And then you can have this line:
where only one of them is unused. |
Ok, can I parse up to a line with a cursor then? Will / can that be made to work? Or, maybe if I'm doing that,
Yes, this is correct, the current approach will replace both. Having columns would also be appreciated. |
Heck let's say that the code does compile ; I'd even be cool with having something that inserts a cursor into the AST at a position I specify so I can pull things out easily. |
Exactly but my point in this earlier comment is that stitching the result of container_cursor_to_quoted back into the original code will be equally hard. I don't think you can escape from going with the full AST and then finding the particular variable by line and columns.
The code does compile though. Otherwise we would not have gotten warnings. :)
I think you should be able to do that with container_cursor_to_quoted. You need a function that count lines, count columns, and compute the byte offset. Pseudo code:
I still think, for this particular case, the textual approach is simpler. I wonder if we should make this part of the existing diagnostics? What if we have a |
I know, I was saying, "let's forget the case when it doesn't compile". We have a couple cases here, the concrete warnings-based case and the abstract "i have a general fix for something" case.
That would be cool indeed. I really understand what you're trying to accomplish here, and it indeed might be simpler in this particular case to apply the fix textually, but I kind of have some lofty goals for what lexical will be able to do, and having a single way to patch and update code is very core to that. In the future, I'd like to distribute a library that enables authors of other libraries to generate diagnostics / generate code actions and refactorings and other things just by including our library and implementing some functions. Exposing a single API for source code translation will make this a lot easier. I've been bitten a lot by the strategy of textual replacement in code in the past, and perhaps my scars are showing. I think I actually have enough written in lexical right now to support the suggestion you gave above. I'll play with it and see and report back to you. |
@josevalim I played around a little bit and it's so close, but I don't think it's going to work. Perhaps you can shed light on what if anything I'm doing incorrectly. defmodule UnderTest do
def func(thing) do
case thing do
{:ok, unused} ->
:ok
_other ->
:error
end
end
end I sent everything up to and including line 4 {:defmodule, [line: 1],
[
{:__aliases__, [line: 1], [:UnderTest]},
[
do: {:def, [line: 2],
[
{:func, [line: 2], [{:thing, [line: 2], nil}]},
[
do: {:case, [line: 3],
[{:thing, [line: 3], nil}, [do: {:__cursor__, [line: 4], []}]]}
]
]}
]
]} This is so tantalizingly close, but the part of line 4 that I would like to examine ( |
Alternately, since the thing compiles, I can just use |
I understand your side too. You are not trying to fix the particular variable renaming problem, you want to provide a framework for people to solve this problem. And solving the variable problem is a way for you dogfood your own API. I think both problems are worth solving!
Yes. 100% the way to go and that solves a whole bunch of other issues. If you want to use container_cursor_to_quoted, put the cursor immediately after the variable (and skipping the rest of the line). |
Yep, that's exactly it.
I'm going to play around a bit, and I wonder if I can provide a lens-like approach where the whole document gets parsed, but we're able to limit the changes to a range to be examined, changed and pulled back out when turned back into a string. |
@scohen can you provide examples of other warnings and errors given by the Elixir compiler that could be automatically fixed? |
Is there a place in the code where errors and warnings are enumerated? I suspect a lot of them can be fixed not completely automatically, but possibly with some user intervention. One candidate would be "this clause cannot match because the other on line x always matches". The fix would be to swap the branches. This seems icky and scary, but keep in mind, the user would have to approve the action. |
@josevalim Another thing that can sometimes be fixed are missing ends. We currently parse the error message so we can see the source of the error, for example: Enum.map(foos, fn x ->
x + 1
) We will highlight both the ending paren, which is where the diagnostic is emitted and also the beginning |
You can search for |
thank you! |
@josevalim I have another ask, I'll describe the problem here and we can create another issue if you so desire.
I was looking at a bug in lexical with code actions. Given the following:
The second line will report an unused variable,
unused
. A LSP server can then emit "code actions" that fix these problems, mainly by replacing the variables with an underscore. The way this is currently implemented is as follows.{:ok, unused} ->
Edit
For this bit of code, the above steps fail at the second step, as
{:ok, unused} ->
is not parseable by any of the methods that I'm familiar with. I currently use ElixirSense to parse because it fixes errors, but it can't handle this. I also triedCode.string_to_quoted
which failed (obviously) as did everything inCode.Fragment
.Previously, I asked for a function in
Code.Fragment
that would emit the AST until the cursor position. I think that would be helpful in this case as well. Alternately, having the ability to parse a chunk of code that's known to likely be invalid would also be helpful, but I think I understand why this will be much harder.Originally posted by @scohen in #12566 (comment)
The text was updated successfully, but these errors were encountered: