Future of Diagnostics UX

Hi all,

Lately, I've been thinking about how the diagnostics user experience for Swift could be improved in the long term. I thought it would be valuable to start a conversation around what kind of improvements people would like to see and what we can learn from other compilers, maybe resulting in some kind of rough roadmap if there's enough interest.

Note: when I refer to diagnostics UX, I'm referring more to how diagnostics are structured and presented to the user as opposed to how they are generated (for example, through the new type checker diagnostics framework).

What follows are some miscellaneous ideas for possible improvements. When evaluating these, I think it's worth considering both how they fit with the motivations of the existing diagnostics style, and how high impact they are relative to the effort required to implement. I look forward to hearing everyone's opinions on this and other ideas you have to improve this part of the compiler!

  • Extended Diagnostic Messages (inspired by Rust): Rust has a feature which lets the user run rustc --explain followed by an error code to view a more detailed description of the error. These messages include a more detailed description, minimal examples of broken and fixed code, suggestions for how to fix the issue, and additional information about relevant related concepts. These can be very helpful for beginners to the language or more experienced users encountering a new error for the first time. For example, the error protocol 'P' can only be used as a generic constraint because it has Self or associated type requirements, which currently appears in over 200 stack overflow questions, might benefit from a longer explanation which wouldn't fit in a traditional Swift diagnostic. They also fit nicely with the idea of progressive disclosure. However, adopting a feature like this would require a large effort.
  • Compiler error index: Closely related to the above, Rust has an online error index which can be a useful reference when looking up an unfamiliar error.
  • Changes to output formatting: Both Rust and Elm make some interesting choices when formatting error output that we might be able to learn from. These include easier to parse line numbering and tying notes more closely to the parent error/warning. For example, this code:
let y = 0
var y = 0

Currently produces:

hello.swift:2:5: error: invalid redeclaration of 'y'
var y = 0
    ^
hello.swift:1:5: note: 'y' previously declared here
let y = 0
    ^

But might be easier to read if it was formatted as:

hello.swift:2:5: error: invalid redeclaration of 'y'
  1| let y = 0
   |     ^ note: 'y' previously declared here 
  2| var y = 0
         ^

Syntax highlighting code excerpts might be valuable as well when color diagnostics are enabled.

  • Type Diffing: Clang has an interesting template diffing system described here which it uses to produce more easily readable messages. It might be interesting to try something similar for generics and tuples in Swift. SR-10291 has some existing discussion of this.
26 Likes

I'd really like to see it have a concept of when the warning/error is an actual mistake vs. when it is probably due to something just still being imputed. So for example, many errors could wait until I have moved to the next line to show, and variable unused warnings could wait until the cursor has exited the method...

4 Likes

That's an interesting idea! It might require some kind of cooperation with IDEs, but seems feasible at first glance

I don't get it. It may be easier to read, but miss a critical information which is the location of the previous declaration.

Without it, this diagnostic is mostly useless.

edit: OK, I see it, but can you give an example with declarations in 2 different files, which is very common.

Sure, I think in that case it's fine to fallback to a format closer to what we have today. I haven't put a lot of thought into it yet, but maybe something like:

hello.swift:2:5: error: invalid redeclaration of 'y'
  2| var y = 0
         ^
world.swift:1:5: note: 'y' previously declared here
  1| let y = 0
         ^

And for diags far apart form each other in the same file, maybe copy Rust and use something like:

hello.swift:2:5: error: invalid redeclaration of 'y'
   1| let y = 0
    |     ^ note: 'y' previously declared here 
   ...
  18| var y = 0
          ^

That said, there are plenty of different variations we could experiment with. My goal in bringing this up was to improve formatting of code excerpts and tie a note more closely to it's parent diagnostic visually, I'm certainly open to other ways we could accomplish that.

Another potential area of improvement I stumbled upon: there are a handful of diagnostics today where messages rely on terminology that afaict isn't documented in The Swift Programming Language or any of the other public docs. Some examples I found were references to nominal types and name mangling, neither of which are particularly hard to understand, but it would be nice if they were explained somewhere for people encountering them for the first time.

On an unrelated note, I've been putting some more thought into what extended diagnostic messages might look like, and may try to put together a more formal pitch for those in the next week or two.

4 Likes

Compiler should tell you what does it mean when a type is called _ when it shows up in error message

2 Likes

This must be one of the single most annoying things about the Swift compiler. Type errors with generics are almost useless because of this.

1 Like

That issue is mostly orthogonal to what I’m discussing here as it’s related to how the type checker recovers when it fails to solve an expression. The good news is it’s already been reduced by the move to the new type checker diagnostics framework and should eventually go away entirely. As a result, I don’t think it’s worth trying to improve the presentation of those messages to the user in the short term.

1 Like