JamesWidman,

i want to play with zig more, but for me the main stumbling block is the absence of a sane LSP server.

(If you're writing an LSP server and you find yourself implementing your own semantic analysis instead of using the compiler's semantic analyzer, you fucked up.)

JamesWidman,

(the same is also true of re-implementing your own parser when you could just use the compiler's, but people get weird about that part—as if it's sensible to duplicate that effort (which includes all of the special handling of syntax errors that make good diagnostics possible, not to mention familiar to the user).)

dgregor79,
@dgregor79@sfba.social avatar

@JamesWidman This really depends a lot on how good the underlying parser and semantic engine are at dealing with broken code and returning answers quickly no matter what. If your compiler has lots of type inference that can be slow sometimes, or starts to fall apart when there are missing or severely broken inputs (say, a missing header/module with all the types you need), it might be a crappy foundation to build on.

JamesWidman,

@dgregor79 Ok, there's a lot going on here, so i'm going to start with something that seems relatively easy...

JamesWidman,

@dgregor79 A missing header (or module) is usually categorized as a fatal error. Many would-be diagnostics against the following code (e.g. "undeclared type 'T'", where T is declared in the missing header) would be fixed by providing the missing header/module.

So the ideal diagnostic output in a case liked that (if there was nothing ill-formed before the errant include/import) is exactly one error message (and no diags about any following code, even if it's ill-formed for other reasons).

JamesWidman,

@dgregor79 this is what clang does*, and i'm glad that clang behaves that way*! (And i don't know why clangd tries to process stuff beyond a broken include; e.g. an error about a use of 'T' is just noise that can only distract me from the real problem!)

[*] or seems to

JamesWidman,

@dgregor79 So what i want in a case like this is for the LSP server to give the same diagnostic output as the compiler. I neither need nor want to use any LSP features in any region of code that follows an ill-formed directive. In that moment, all i need is to focus on why the include failed and how to fix it.

(and similarly for an ill-formed module import.)

Am i missing something?

dgregor79,
@dgregor79@sfba.social avatar

@JamesWidman So you have a project that had generated headers in it somewhere, and if it hasn’t been yet (or recently enough), you’re happy to just have… nothing? No code completion, syntax coloring, go-to-definition, nothing? Even for obvious stuff in the same file? This is not something I’d be okay with, and the user reports we get imply that most users expect their IDEs to keep being useful even in t e presence of errors in the code.

It’s certainly possible to build the foundations of your compiler so that they can be used by IDE tooling, but you don’t get there by treating the IDE like a compiler. I think it’s the other way around—build your compiler like an IDE service, one function of which is to produce machine code.

JamesWidman,

@dgregor79
1)
> So you have a project that had generated headers in it somewhere, and if it hasn’t been yet (or recently enough), you’re happy to just have… nothing? No code completion, syntax coloring, go-to-definition, nothing? Even for obvious stuff in the same file?

That's correct!

My solution in this scenario (and i urge everyone to consider the possibility that it's the only correct solution) is to do whatever is normally done to generate the generated headers (e.g. cmake ---build).

JamesWidman,

@dgregor79

> the user reports we get imply that most users expect their IDEs to keep being useful even in t e presence of errors in the code.

Oh, i lived through 11 years worth of reports like that! Users wanted to use my employer's static analyzer to find bugs in their C/C++ code. They frequently didn't do what's necessary to configure the preprocessor, so they immediately hit things like "header not found"; downstream errors resulting from missing predefined macros; etc...

JamesWidman,

@dgregor79

Their knee-jerk solution? Suppress those error messages.

[head-desk]

They would say things like, "I don't want to do static analysis on the standard library headers; i want the tool to analyze my code."

So i frequently had to (re-)explain that a correct parse/sema of the library code is a prerequisite for a correct analysis of their project code.

JamesWidman,

@dgregor79
The fact that most users believe X does not imply that X is true!
When X is false, it may be that the best way to help them is to explain why X is false.

(Though ideally, we would like to set things up so that e.g. the cmake --build happens automatically, and before they try to do anything that depends on generated headers.)

dgregor79,
@dgregor79@sfba.social avatar

@JamesWidman it’s a silly principle to stand on when code completion fails to even show you local symbols due to one error. Tools that work with necessarily-incomplete code should degrade gracefully, not hard-fail.

JamesWidman,

@dgregor79

> [...] due to one error.

but a missing header isn't just any kind of error; it's not like an ill-formed type conversion. It belongs to a category of error that indicates that subsequent sema is going to be incorrect, which would lead to many cascading errors. That's why it's a fatal error!

JamesWidman,

@dgregor79
And it's not as if people normally lose hours or days to a missing-header error. You do the build to generate headers (or supply the missing -I option in the build config, or whatever), and you move on. That seems like a small effort to ask of the user, and their experience afterward will be better for it.

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79 I feel like you aren't really listening to what Doug has said the users of IDEs are saying, or to an extent what your own users were saying.

If you've seen so many user reports, then I do think your users have lost hours to a missing-header issue. That's the only way they'd take the time to report it to you.

FWIW, all of the research we've done with our users matches what Doug reports -- users struggle when basic IDE tools stop working due to a missing header.

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79

And I don't think the right takeaway is to tell the users they need to change and learn a new approach.

That is about the most expensive solution I can offer. The cost scales for all time with new users or users that forget. And asking people to change is hard. Sometimes necessary, but I don't want to spend their precious retraining time and energy on how to interact with IDE tooling. Bad ROI.

Instead, we should meet users where they are by gracefully degrading.

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79

And this in turn means that the basis of the IDE tooling needs to be designed with this in mind, all the way down the stack.

Sometimes, there will be a layer that makes sense to intercept with the compiler design. Great!

Sometimes, there won't, because this constraint forces the tooling to solve a slightly different problem.

Notably, sometimes (parts of) the IDE needs to focus on a subset/different language because the language wasn't designed to enable this.

JamesWidman,

@chandlerc @dgregor79 sorry; i mean: once you've learned how to correctly deal with a missing header, it doesn't take hours to fix that kind of error when you see it again.

We certainly could have done a better job in terms of documentation, and in terms of helping people learn how to deal with missing header errors (and other misconfiguration errors)!

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79

Having watched a lot of users (and read surveys of more users) fixing missing include issues, my expectations do not match that.

The fixes often involves manipulating different parts of the build system that fail in different ways for different reasons and users do not develop effective understandings of how to correct the second or third issue from the first. It often takes more time than the task they set out to do (an average bug fix let's say).

JamesWidman,

@chandlerc @dgregor79 i'm sympathetic on this point (especially where the error messages are downstream from the root cause)...

It's just that "graceful degradation" (https://hachyderm.io/@chandlerc/112141788393174550) can't be a long-term solution, because it conceals the root cause of the problem and leads to more downstream issues.

But I do like the idea (https://hachyderm.io/@chandlerc/112141798407744234) that language design could help. Maybe some language designs could help guide the user toward solutions to the root cause!

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79

I think the degree of accuracy and utility provided by, for example, tree-sitter grammars for a large number of languages show just how effectively a gracefully-degrading parser can work to implement much (but not all) LSP functionality, without producing spurious or confusing error messages.

They do gracefully degrade. But they were designed that way, and at times deviate from the underlying language rules to achieve it (a good thing IMO!).

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79 The more nuanced approaches taken in modern LSPs for modern languages (Swift and Rust at least) I think are even more powerful, at some complexity cost. But they, again, embrace graceful degradation to be effective.

Even ClangD does -- it uses hilarious amounts of recovery to go past missing headers and such in order to provide functionality, and even has multiple layers that can be used independently to fill gaps here due to the language design headwinds.

JamesWidman,

@chandlerc @dgregor79
> Even ClangD does -- it uses hilarious amounts of recovery to go past missing headers

i would love to find the configuration switch for this so that i can disable it!

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79 I don't think there is one.

And I suspect it doesn't make sense for to add or support a configuration switch to disable something that has near universal demand from their users -- that's a significant complexity for what all research I have seen indicates is a near universally perceived as worse user experience. =/

I understand, cold comfort for someone who might be the exception.

JamesWidman,

@chandlerc @dgregor79

i feel like, for now, we're going to agree to disagree about fatal errors (although users definitely deserve much better help than they've been given on this point so far)...

But this kinda segues into another of my concerns following from Doug's first post above (about an LSP rolling its own sema):

https://mastodon.social/@JamesWidman/112139711036599684

JamesWidman,

@chandlerc @dgregor79 basically: one of the things i like to use LSPs for (or at least clangd, which now seems like it might be an outlier among LSPs in this regard) is to read well-formed code with the assistance of e.g. type info on mouse-hover so that i can build an understanding of the compiler's view of the semantics of the program that i'm reading.

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79 This makes sense, but FWIW, ClangD is a bit of an outlier there AFAIK. Even for C++ -- I know of two other LSPs, neither built on the same batch compiler logic.

But I understand your concern. When you duplicate the semantic logic, it can diverge. And when developers are exposed to that divergence, it hurts.

This is a real engineering trade-off, but I don't think it yields a clear single answer. While that is a real risk for an LSP, so is the header-missing UX.

JamesWidman,

@chandlerc @dgregor79
So if an LSP server is not using the compiler's sema, i don't see how to trust its semantic analysis.

Like: if the two semas were to diverge for a given input, how would i know?

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79 I mean, why do you trust ClangD at a different version from the Clang compiler? (A necessity if you support multiple versions)

Or why trust ClangD's model rather than looking at assembly -- the code gen layer or LLVM may disagree with what ClangD says. We've even seen bugs like this, where we generated code that could reach more destinations than source analyses built on Clang thought were reachable.

There's risk in trusting an abstraction. But you need abstractions.

JamesWidman,

@chandlerc @dgregor79

> I mean, why do you trust ClangD at a different version from the Clang compiler?

well... I wouldn't.

I do check to make sure that the clangd that i use comes from the same bin directory as the clang++ that i use to compile!

(i realize i might be a weirdo on this point.)

JamesWidman,

@chandlerc @dgregor79
> Or why trust ClangD's model rather than looking at assembly -- the code gen layer or LLVM may disagree with what ClangD says.

wait... what?

i'm just trying to do everything i can to get the ClangAST generated by clangd to match the ClangAST generated by clang during the compile. (Ideally, they'd be guaranteed to be the same, e.g. cached during compilation and then read from disk by clangd, though ofc while modifying src that isn't feasible [edit: or is it...?])

JamesWidman,

@chandlerc @dgregor79 if some analysis (that takes a clangAST as input) produces a different result than LLVM, that's unfortunate, but at least i have relative confidence that i'm seeing the correct phase-7 info about types & decls on mouse hover!

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79 Probably.

My point is that IDE tooling isn't engineering with the primary constraint of constructively assuring that is the case. It is instead trying to achieve a more subjective goal of a good UX for the vast majority of interactions, which is only loosely coupled to exact match of phase X on entity Y.

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79

Most users spend a large fraction of their IDE time w/ incomplete code that does not (yet) compile. As you noted, we can't give constructively correct phase X for that, but it is much more important to give good UX. An LSP can and should prioritize a reasonably helpful mouse-over in incomplete code over a perfectly correct one in all edge cases of complete code.

That may mean inaccuracies in your usage that are not considered bugs by ClangD or any other LSP. =/

JamesWidman,

@chandlerc @dgregor79 maybe the reason why i hadn't considered that case is because, when my code is below some level of completeness (e.g. when writing calls to functions that i haven't declared yet because i'm toying with a new API design), i'll just toggle the LSP server off. (I have a keybinding for that. It's nice to try out new API designs without the red squigglies! Also it keeps sema from getting sad.)

chandlerc,
@chandlerc@hachyderm.io avatar

@JamesWidman @dgregor79 AFAIK, this is both very rare and not something any LSP I know of is designed around.

Not saying your use case is invalid, just clarifying that I feel like you are using the LSP as an incredibly specialized tool for a very specific and narrow workflow that happens to mostly overlap the broad tool/workflow that exists.

Noticing the mismatch doesn't mean either is wrong in and of itself, but instead that the expectations of your workflow aren't reasonable for the tool.

  • All
  • Subscribed
  • Moderated
  • Favorites
  • random
  • ngwrru68w68
  • rosin
  • GTA5RPClips
  • osvaldo12
  • love
  • Youngstown
  • slotface
  • khanakhh
  • everett
  • kavyap
  • mdbf
  • DreamBathrooms
  • thenastyranch
  • magazineikmin
  • megavids
  • InstantRegret
  • normalnudes
  • tacticalgear
  • cubers
  • ethstaker
  • modclub
  • cisconetworking
  • Durango
  • anitta
  • Leos
  • tester
  • provamag3
  • JUstTest
  • All magazines