Compiler Errors for Humans (2015)

(elm-lang.org)

80 points | by todsacerdoti 952 days ago

17 comments

  • shadowofneptune 952 days ago
    I'm reminded of these error messages from an Apple C compiler:

    https://www.cs.cmu.edu/~jasonh/personal/humor/compile.html

    - "String literal too long (I let you have 512 characters, that's 3 more than ANSI said I should)"

    - "...And the lord said, 'lo, there shall only be case or default labels inside a switch statement'"

    - "You can't modify a constant, float upstream, win an argument with the IRS, or satisfy this compiler"

    Some of them are more helpful than others, but they are all rather human.

    • mannyv 952 days ago
      Too many errors on one line, make fewer.
      • LoganDark 952 days ago
        I don't regret reading this comment before clicking the link
    • mannyv 952 days ago
      There was one that went something like "a typedef (or something) at this point was a complete surprise to me.'
    • capableweb 952 days ago
      Better than the infamous PHP error message made in Hebrew: T_PAAMAYIM_NEKUDOTAYIM
    • TheBrokenRail 952 days ago
      While those are funny, I'd hate using a compiler that actually gave me error messages like that.
  • kibwen 952 days ago
    Elm's error messages were the explicit inspiration for Rust's current error messages: https://blog.rust-lang.org/2016/08/10/Shape-of-errors-to-com...
    • convolvatron 952 days ago
      everyone opines about lovely rust error messages are. but in there is 'you really need to add lifetime annotations'. advice, which is followed, will result in days of mad refactoring, only to discover that was a _really bad plan_.

      pure evil

    • dmitriid 952 days ago
      I think they were an inspiration for most modern languages. Either directly or indirectly.
  • eckza 952 days ago
    After shipping several production applications in Elm over the last five years... I can confirm that the development experience is an absolute joy.

    With a compiler this helpful, and sub-second builds-on-save - you get really tight feedback loops.

    I can't say enough good of it.

    • capableweb 952 days ago
      If you enjoy tight feedback loops and want something even quicker than "rebuild on save", you should try something like Clojure and hook up your editor to a REPL running in the background. Rather than "recompile the whole program on save", you can send small snippets to be evaluated in the live environment, updating your program on the fly :)
      • tmtvl 952 days ago
        I was fairly happy doing REPL development with Scheme, and then I decided to give Common Lisp a try and I discovered what REAL REPL-driven development is.

        The first time I changed some data representation from a defclass to a defstruct and SBCL asked me what I wanted to do with existing instances of the type was mindblowing.

  • dtgriscom 952 days ago
    In the early '80s at MIT, we learned the CLU [0] language. The wonderful aspect of that compiler is that when it hit a syntax error it would suspend compilation for a few dozen lines, or whatever it took to get past the blast radius of the error, and then continue compiling, looking for further errors. You'd see the error message on the faulty line, and then a "... resuming compilation here" message showing where it would start checking for more error messages.

    You (obviously) wouldn't get a useable binary, but at least you'd see more than one error at a time, without the cascading sequence of errors you see in (most?) current compilers.

    [0]: https://en.wikipedia.org/wiki/CLU_(programming_language)

  • seanwilson 952 days ago
    > Folks who prefer dynamically-typed languages are generally of the opinion that working with compiler error messages sucks.

    I think a big factor here is dynamic languages can show concrete variable instantiations to help you understand what went wrong so it's less abstract e.g. the code `plus x y` might give the runtime error "x was 'abc' which is type String and not type Number" vs something more abstract and confusing like "x is type String and not type Number" that you tend to see in compile-time errors.

    Are there any languages that give concrete variable instantiation examples as part of compile-time error messages? For instance, example values could be generated from the types, come from previous program runs, or supplied by the coder somewhere.

    When errors get tricky, you tend to start plugging in concrete values or stepping through the program manually so compilers could definitely help more here.

  • AlbertCory 952 days ago
    Favorite experience with compiler errors: C++ in 2004 or so, when the errors were so voluminous and inscrutable that you had to install another program to massage them into something readable.
    • cratermoon 952 days ago
      The first time I tried writing a C++ program with templates (very early on, like mid-late 90s), the compiler error messages were longer than the little program I was trying to write. It put me off on the language and the compiler so much that I went back to plain C and off into Perl and other languages, and never really got back to it.
  • renox 952 days ago
    I still wonder why editors don't understand line numbers on the CLI <editor> file:5

    The editor should try to find if 'file:5' exist and open it if so, if it doesn't it should try to open 'foo' and then go to line 5.

    Sounds sensible no? But I don't know any editor which does this.

    • masklinn 952 days ago
      > The editor should try to find if 'file:5' exist and open it if so, if it doesn't it should try to open 'foo' and then go to line 5.

      Opening a file is often done in creation, and `file:5` is a perfectly valid filename, so it's a bit debatable for a default.

      OTOH in Emacs you should be able to define a find-file-not-found-functions hook[0] and implement whatever fallback you want. I assume `emacs <file>` calls `find-file` after it's initialised the editor itself.

      [0] https://www.gnu.org/software/emacs/manual/html_node/elisp/Vi...

      • Measter 951 days ago
        > Opening a file is often done in creation, and `file:5` is a perfectly valid filename, so it's a bit debatable for a default.

        It gets even more fun on Windows! Giving a filename of `foo:5` can result in a file called `foo` with an alternate datastream called `5`.

      • renox 952 days ago
        > Opening a file is often done in creation, and `file:5` is a perfectly valid filename, so it's a bit debatable for a default

        You're right, but even as an option I think it'd be nice.

    • euiq 952 days ago
      Visual Studio Code does this (see "File links"): <https://code.visualstudio.com/docs/terminal/basics#_links>
      • renox 952 days ago
        I don't think it works from the CLI: code <file>:5 doesn't do what's expected.
        • Measter 951 days ago
          You have to use the `-g` command line option: `code -g <file>:<line>`
          • renox 951 days ago
            Thanks I didn't know this.
          • 1-more 941 days ago
            huge, tysm
    • yakubin 952 days ago
      That works in Emacs compilation buffers. You can also define custom patterns to match the output formats of different compilers.
    • LtWorf 952 days ago
      It works with kwrite/kate/kdevelop.
    • matsemann 952 days ago
      JetBrains products do this for lots of stuff if you use the built in terminal to launch stuff or their runner.
    • oftenwrong 952 days ago
      vim does:

          vim file +5
      • renox 952 days ago
        But compiler error messages are file:5 so this isn't good enough.
        • imran-iq 952 days ago
          That's why you have `:make` which uses `errorfmt` to populate your quickfix list, which is covered in the help `:h make`
          • renox 951 days ago
            I'm not sure if you're joking or not but if you're not: I usually don't call the compiler from my vim session..
            • imran-iq 950 days ago
              Right but if you want to edit and fix files that failed compilation, using `:make` and the quickfix list is the vim way of doing it
  • BoppreH 952 days ago
    Should a compiler also suggest applying the fix?

    I'm working on my own toy language and wondering if simple, obvious errors (like the `map` -> `nap` typo from the article) should come with an "Apply fix? [y/N]" option when run interactively.

    • masklinn 952 days ago
      Maybe as an opt-in (mode or blanket approval), having to continue on every compiler error which has a fixer would be rather frustrating.

      Clippy supports fixes, but will only apply them if `--fix` is supplied. It does not ask for individual case, the assumptions likely being that if you're running this opt-in you can probably do so with a clean working copy and revert whichever fixes you didn't want or are incorrect.

      An other issue is if you're providing fixers for suggestions, the fixer has to be extremely reliable, not a 90% thing, because replacing broken code by possibly subtler broken code is not great.

    • epage 952 days ago
      On top of what others said, cargo is looking to further improve the fix workflow.

      Right now, some errors offer fixes and these are marked machine-applicable. cargo-fix will apply these. The workflow was originally written for Edition migrations and has had little attention past that. Changes are applied in bulk and the worktree must be clean. There are known bugs and it isn't trusted to fix errors.

      We want to slowly raise the visibility so we can collect feedback and gain more confidence in it. The first thing we did is tell users when there is something for cargo fix to do when getting compiler errors. Currently, this is limited to warnings and the nightly toolchain. I look forward to expanding this to more users (myself included).

    • jamesmunns 952 days ago
      Both Rust (via 'cargo fix') and Clang (via `clang-tidy -fix` have ways of doing this, I don't think I've seen someone offer this automatically/interactively though.

      I don't think I've ever used them, but I've ABSOLUTELY used suggested fixes from LSP/rust-analyzer tools, which fit much more into my typical workflow. For things like match statements in Rust (which need to be exhaustive), I now have muscle memory to just write an empty match statement, and trigger the first LSP/r-a suggestion, which fills the match block with placeholder items.

      I guess I see LSP as my "compiler in interactive mode" interface (even if that isn't EXACTLY true).

  • dang 952 days ago
    Discussed at the time:

    Compiler Errors for Humans - https://news.ycombinator.com/item?id=9805978 - June 2015 (90 comments)

  • warpech 952 days ago
    I collect examples of systems that have helpful error messages. Here's a blog post (not mine) that praises error messages in React [1]. Any other examples?

    [1] https://codeburst.io/the-true-delight-of-reacts-error-and-wa...

  • boxed 951 days ago
    To actually get good errors in Elm you need to not use type inference and declare explicit types in lots of places. A big part of this is due to currying as any mistake in the number of parameters passed to a function is never type checked at the call site, but always when the accidental type collides with some other type.
  • hsuduebc2 952 days ago
    I sometimes remember horrors of "undefined index of null" which are for new developers quite frustrating. Good job!
  • squaredot 952 days ago
    I would love to have something like this. With Julia, a programming language that I like very much, it's really a pain.
    • salty_biscuits 950 days ago
      I have high hopes for this project

      https://github.com/JuliaLang/JuliaSyntax.jl

    • blindseer 952 days ago
      The first thing I thought when reading this blogpost was "Can I send this to a Julia core dev? Would they understand how much nicer error messages are in other ecosystems?"

      I think even basic things like the order of error messages is all backwards to me. Take this very silly example:

          julia> foo() = println(123 * "hello")
          foo (generic function with 1 method)
      
          julia> bar() = foo()
          bar (generic function with 1 method)
      
          julia> baz() = bar()
          baz (generic function with 1 method)
      
          julia> baz()
          ERROR: MethodError: no method matching *(::Int64, ::String)
          Closest candidates are:
            *(::Any, ::Any, ::Any, ::Any...) at operators.jl:591
            *(::T, ::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} at int.jl:88
            *(::Union{AbstractChar, AbstractString}, ::Union{AbstractChar, AbstractString}...) at strings/basic.jl:260
            ...
          Stacktrace:
          [1] foo()
            @ Main ./REPL[3]:1
          [2] bar()
            @ Main ./REPL[4]:1
          [3] baz()
            @ Main ./REPL[5]:1
          [4] top-level scope
            @ REPL[6]:1
      
      In Julia the type of the error is printed out immediately at the point where the error occurs, then a bunch of hints about closest candidates, then the function and filename where the error occurred, then the function that called it, then the function that called that. It's all backwards. In order, it's

      1. important information 2. arbitrary hint which could be useless 3. most important (where the error occurred) 4. less important 5. less less important

      When the stacktrace is long, this is so painful to deal with in a REPL environment. You almost always have to scroll to find out information about the error, and you have to scroll just the right amount, or else ...

      And even the stacktrace arguably doesn't contain all the information it should, demonstrated well by this blog post how nice it could be.

      VSCode is preferred way of using Julia (for me and for better or for worse for everyone). For long stacktraces I have to first scroll all the way back up to see what is going on. So often I have a very tiny terminal open at the bottom of my screen, and it is EXTREMELY annoying to do that. I often scroll too much and I overshoot, and just finding the error is a challenge. There's so many times where I'm just struggling to find the error, and it's just an exercise in frustration to be honest.

      Here's the same example in Python.

          In [1]: def foo():
            ...:     print("1" + 1)
            ...:
      
          In [2]: def bar():
            ...:     foo()
            ...:
      
          In [3]: def baz():
            ...:     bar()
            ...:
      
          In [4]: baz()
          ---------------------------------------------------------------------------
          TypeError                                 Traceback (most recent call last)
          Input In [4], in <cell line: 1>()
          ----> 1 baz()
      
          Input In [3], in baz()
                1 def baz():
          ----> 2     bar()
      
          Input In [2], in bar()
                1 def bar():
          ----> 2     foo()
      
          Input In [1], in foo()
                1 def foo():
          ----> 2     print("1" + 1)
      
          TypeError: can only concatenate str (not "int") to str
      
      
      The last line tells me the error. The line before that tells me where. I can scroll up to find more information if I want to.

      Why doesn't Julia work like this? Who knows. I've made suggestions on slack and to people in person but I've been met with disdain for the most part.

      Not to mention that almost ALL my errors are MethodError types.

      And worst of all, Julia doesn't show you the string of your code in the error, so you have to click on the line that has the file and hope VSCode opens that file at that line. It's SO backwards. If you are using neovim / vim / tmux, you have manually copy paste the line that contains the file name and line number into a new terminal. Or just navigate to the file and line number manually. Ugh. In Python, I can see the error and know what caused the error. In Julia, I see the error, kind of sort of know what might be the problem, try to find the exact file and line, check if my assumptions are correct, if not traverse up the method dispatch call stack and try to predict what might be going on.

      And I consider myself an experienced Julia developer. For my team members coming from Rust or Python when they get a error running code, it's just brutal during the learning process. I've had people come up to me and say to my face, Julia sucks and I shouldn't write or advocate it anymore.

      This is barely touching the surface. Error reporting with macros is even worse. My team has managed to segfault our program multiple times and we are left completely in the dust then.

      There's SOOOOO many examples like this where I think usability in Julia needs to be improved. Sigh. Maybe some day.

      • adgjlsfhk1 951 days ago
        Yeah we really need to do better on this. One thing that would help a lot is if we moved error messages to a pager system since it would make it a lot easier to jump to beginning/end of error message. A lot of this work is relatively accessible for people new to hacking on Julia's internals if anyone wants to take a shot at it. I think a lot of the reason Julia is lacking some tooling compared to other languages is that Julia attracts a lot of people who don't have traditional CS backgrounds which affects the types of tools that get written. We have state of the art diffeq solvers and really good remote calls to C/Fortran, but the dev tools aren't as mature as I wish they were.
  • robinsonb5 952 days ago
    I suspect it's accidentally rather than deliberately human, but the GCC warning about C++ classes with non-public destructors made me smile the first time I saw it - it ends with the words "and has no friends"!
  • blep_ 952 days ago
    > You also often get a pretty-printed version of the problematic code, but it looks nothing like the code you wrote

    What compilers do this? Every one I can think of either shows nothing or a verbatim line of code.

  • pizlonator 952 days ago
    Those are some super nice error messages!

    Worth calling out that clang does an awesome job at this, by doing some of what Evan talks about.

  • Existenceblinks 952 days ago
    I seem to have an unpopular opinion about humanized compiler error messages. It's better than cryptic messages for sure but I think it's worse than robotic but concisely clear error messages. For example, for a type error, don't write mini book for me, just tell me "line: 56 column: 97, s/int/string/". Same for complicated error, don't try to be human it's even more confusing.
    • jdkoeck 952 days ago
      > line: 56 column: 97, s/int/string/

      Sorry, but that’s the least accessible error message I have ever read.

      • Existenceblinks 952 days ago
        I suspect the downvoter misinterprets my "example". s/int/string/ meant to be semantics. Something like diff in test or text diff with semantic help.
      • capableweb 952 days ago
        You haven't dealt with PHP by any chance, have you?

        Not saying the above is good/bad, just saying that there are (was) worse offenders out there for sure.

        • Existenceblinks 952 days ago
          Half serious, I think JS' "undefined is not a function" wouldn't be terrible if it tells me where it lost its value. The message itself is already ok.
    • cloogshicer 952 days ago
      Out of genuine curiosity: Why do you think that? Trying to understand where you're coming from.
      • Existenceblinks 952 days ago
        I wrote Elm before and while the error message is nice it takes longer to iterate and fix stuff. Because It's in a mix mode of docs and error message. I only want a clear error message concisely.
        • 1-more 941 days ago
          I'm with you to an extent: I'd like to drop the "Hint" and "Note" in the error message optionally.
        • cloogshicer 952 days ago
          Interesting, thanks for explaining.